快速了解javascript中的关键字_“this”’

1,457 阅读6分钟

this 关键字

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

相信所有学习过或者正在学习前端的小伙伴都或多或少接触过this关键字,在javascript中是一个比较复杂的机制,不少开发者也会在它指向什么时犯怵,所以今天我们来谈谈关于this关键字,希望能给正在学习的你或者存在一些疑惑你带来些许帮助。

前言

  1. this关键字是javascript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。而且this是在函数被调用时被绑定的,而不是在编写时被绑定的。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

  2. 当一个函数被调用时,会创建一个执行上下文 (执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象(AO对象))执行期上下文会记录包括函数在哪里被调用(调用栈)、函数的调用方式、传入的参数信息。this就是执行上下文中的一个属性,会在函数执行的过程中用到。

  3. 如何寻找函数调用的位置,从而判断函数在执行过程中会如何绑定this。

this绑定的四项规则

当我们了解了这四个相关的绑定规则时,我相信就这些this的指向问题就不再是问题,首先我们来介绍一下一号选手‘默认绑定’。

1、默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看做是无法应用其他规则时的默认规则;

  • this所处的词法作用域在哪里生效了,this就绑定定在哪里 先来个简单的🌰
function fn(){
    console.log(this.a);
}
var a = 2
fn();
此处函数的调用应用了this的默认绑定,因此this指向全局对象Window;
因此打印结果为2

image.png

但是有一个微妙但是十分重要的细节,在严格模式下,全局对象无法进行默认绑定,所以导致this只能绑定在undefined身上。

image.png

我们再来分析下这个🌰 image.png

在上面的例子里,this绑定在哪里呢,有些同学可能会认为这个this是在函数foo里的,而函数foo又在函数bar中被调用了,所以这个this就是指向bar函数,其实不尽然,从上图的结果可以看出这个this还是指向了全局对象Windows,我们再好好体会一下,这个执行结果是因为baz函数在全局被调用,而bar函数又是指向baz函数,foo函数又是指向bar函数,所以不就是 // baz -> bar -> foo,所以这个this还是绑定到了全局对象嘛。

介绍完默认绑定之后我们来聊聊隐式绑定:

2、隐式绑定

  • 当函数引用有上下文对象时,隐式绑定的规则就会把函数调用中的this绑定到这个上下文对象中;

我们来思考一下下面这段代码:

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
}
// 此时foo函数被引用至obj这个对象中,此时foo引用有上下文对
象,这时隐式绑定规则就将foo中的this绑定到了obj这个对象当中,
即this.a和obj.a是一样的。所以打印出的this.a = 2

obj.foo() // 2

对象属性引用链中只有上一层或者说说最后一层在调用位置中起作用。举个🌰

image.png

3、隐式丢失

一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者是undefined上,取决于是否是严格模式;

我们来思考下这个🌰

function foo(){
    console.log(this.a);
}
function doFoo(fn){
// fn其实是foo
    fn()  <-- 调用位置
}
var obj ={
    a: 2,
    foo: foo
}
var a = 'global'
doFoo(obj.foo)

此时this.a应该绑定在哪个对象上呢? 我们一起来分析看看:

首先this.a是存在于foo函数,而从代码中我们可以看到foo并没有被调用,只是被obj对象引用,所以有同学就会说了这个我知道,此时的this.a就等于obj.a,就是隐式绑定嘛,但是你仔细看代码会发现此时obj.foo被作为实参传入doFoo函数里,此时隐式绑定的函数丢失了绑定对象,doFoo()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值

所以通俗一点说就是: 当隐式绑定超过一次以上时就会导致隐式丢失,会应用默认绑定。

就像我们刚才看到的那样,在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this隐式绑定到这个对象。那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用,那么该怎么做呢?

4、显示绑定

call(...),apply(...),bind 可以强行指定函数的this指向,javascript提供的绝大多数函数以及我们自己所创建的所有函数都可以使用call(...),apply(...);但是bind方法是返回了一个函数,需要我们调用;你可以直接指定this的绑定对象,因此我们称之为显示绑定。

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
//1. foo.call(obj); //将foo的this强行绑定到obj上

//2. foo.apply(obj); 
//3. var bar = foo.bind(obj)
// bar()

但是call(...),apply(...)他们传递的参数有些许的不同,我们来用一个🌰好好体会一下:

var obj = {
    user: 'wn',
    fn: function (a,b) { 
        console.log(a+b);
        console.log(this.user);
    }
}
var b = obj.fn
//从这个例子中我们能够很好的感受到,第一个参数都是要绑定的对象,而call(...)的第二个参数是一个一个参数,
而apply(...)的第二个参数是一个数组,因此需要我们注意的是输入参数时两个方法传入的参数略有不同。

b.call(obj,1,2)  // 3  'wn'
b.apply(obj, [1,2]) // 3  'wn'

var bar = b.bind(obj,1,2)
bar()

var bar = bar.bind(obj)
bar(1,2)

// 而bind方法在构造一个函数时,传入参数的方法与call一样是一个一个参数,但是他可以在构造时传入,也可以在在调用时传入。

5、补充

在ES6新增的箭头函数中是没有this关键字的

function foo() {
   const bar = () => {
       console.log(this.a);
       //this 是foo的this(是外层非箭头函数的this)
   }
   bar();
}
var a =2
foo()

结语

当我们理解好this的四项绑定规则之后,当我们需要判断this的绑定对象时,我们可以试着去分析它属于哪种绑定规则,那么有没有什么方法可以不使用call(...),apply(...)和bind方法来实现显示绑定呢,欢迎在评论区留言讨论,大家共同进步。