this

什么是 this

为什么要用 this

this 可以当做一个隐式传递的上下文,避免每次都需要显示地传递。

function foo(context) {
    console.log(context.a)
}

var obj1 = { a: 1 }
var obj2 = { a: 2 }

foo(obj1)   // 1
foo(obj2)   // 2

上面的代码中,每次都显式地传入了一个 context 上下文。

但我们可以利用 this 来更加优雅的实现上列代码:

function foo() {
    console.log(this.a)
}

var obj1 = { a: 1 }
var obj2 = { a: 2 }

foo.call(obj1)  // 1
foo.call(obj2)  // 2

this 的误区

下面是两种常见的误区:

  • this 指向函数本身

  • this 指向函数的作用域

this 不指向函数自身的对象

虽然在 JavaScript 中,函数也是对象。但 this 并不是指向函数自身的对象。

例如,你想向函数对象内写入一个属性,来记录函数被调用的次数:

在函数内部使用 this.count 并不能读取到函数对象内的 count 属性。

如果要实现类似效果,将 this.count 改成 foo.count

this 不指向函数所在的词法作用域

不要把词法作用域和函数自身对象搞混在一起。虽然他们很相似,但作用域“对象”无法通过 JavaScript 的代码访问,他只能由引擎自身访问。

this 是什么

this 是在运行时绑定的,而不是在编写的时候绑定的。

this 的绑定和函数声明的位置没有关系,只取决于函数调用的方式(在哪里被调用)

当函数在调用时,会创建一个活动记录。包含调用栈,函数调用的方式,传入的参数等。this 就是其中的一个属性。

绑定规则

默认绑定

默认绑定是在无法应用其他规则时的 fallback option

当应用了默认绑定规则,this 指向全局对象

上面的代码函数在调用时,应用了默认绑定规则,因此 this 指向全局对象

如果使用了严格模式,this 会被绑定为 undefined

只有在函数内部应用严格模式才会限制默认绑定规则

相反,在严格模式下调用函数并不会影响默认绑定规则

隐式绑定

函数的引用有上下文对象时,函数中的 this 会绑定到这个上下文对象上:

foo 被对象 obj 引用,调用时 this 绑定在 obj

但是,如果存在引用链,那么以最后一层调用为主:

绑定丢失

在某些情况下,隐式绑定可能会发生绑定丢失。取而代之应用了默认绑定规则

除此之外,在回调函数中也很容易丢失绑定:

即便是传入给内置的回调函数,如 setTimeout 也会发生绑定丢失:

显式绑定

我们可以在一个对象内引用一个函数,并用这个对象的属性来调用该函数,从而使得 this 隐式绑定到该对象上。但是我们也可以使用显示绑定,将 this 绑定到某一特定对象上。

如果传入的是一个值类型,那么 call() 方法会把它包装成一个对象。如:new String(), new Boolean()...

call 接受一个个单独的参数,apply 接受一个参数数组

硬绑定

每次调用 bar 的时候,在他的内部都会调用一次显示绑定,因此 obj 都会被绑定在 this 上面。

new 绑定

JavaScript 中的 new 关键字只是对函数的构造调用,而非构造函数。不要把它和面向对象里的实例化混为一谈

当使用 new 调用函数时,将会发生以下四个步骤:

  1. 创建一个全新的对象

  2. 将这个对象连接到函数的原型对象上

  3. this 绑定到这个对象上

  4. 如果函数自身没有返回对象(返回值类型不算),则返回这个对象(即 this ,它目前就是指向该对象)

new 在 JavaScript 中只是一种函数调用的方式,并不是什么实例化,千万不要搞混!

绑定优先级

如果想要明确一个 this 的绑定,按照以下优先级顺序进行判断:

  1. 函数是否存在 new 构造调用

  2. 是否使用 call bind 进行显式绑定

  3. 是否被某一个上下文对象调用,如:obj.foo()

  4. 上述规则都不满足时,使用默认绑定;严格模式下绑定到 undefined,否则绑定至全局对象

例外

  • null undefined 传入 call apply bind 中会被忽略,转而使用默认绑定

    在某些不需要关心this 是什么的时候特别有用

  • 在间接引用的情况下应用的是默认绑定规则,而不是隐式绑绑定规则

如果想在 call apply bind 中传入 null undefined 来应用默认绑定规则,会有可能造成对全局对象的污染,函数中的某些操作可能会修改全局对象中的属性。

一种更加安全的做法是创建一个 DMZ 对象:

箭头函数

箭头函数的 this 不使用上述 4 种绑定规则。根据外层的词法作用域所决定。

具体来说,箭头函数内的 this 会继承外层函数的 this 绑定:

箭头函数压根没有 this ,他只是继承

如果不使用箭头函数,上述代码在调用 bar() 时,其实会应用默认绑定规则:

又或者,在回调函数中,如果在不使用箭头函数的情况下,很容易在不知不觉中应用默认绑定:

在没有 ES6 箭头函数时,可以使用以下方法解决:

在对象内使用箭头函数

通常,我们会在函数内使用箭头函数;这个箭头函数的 this 会捕获所在函数的词法作用域。

但是,如果一个对象的某个属性持有一个箭头函数呢?就像这样:

Last updated