JavaScript中的this指函数上下文(function context),在函数被调用时自动创建,由函数的调用方式决定。也就是说函数的调用方式决定了该函数中this的值,this叫函数调用上下文似乎更准确一些。

「this」的值由函数的调用方式决定

在JavaScript中,函数有4种调用方式,this值也就对应了4种值:

  • 直接以函数形式调用,this的值为undefined(严格模式)或者global/workerGlobal(非严格模式)
  • 作为对象方法调用,this的值为该对象
  • 使用new操作符调用,this的值为新创建的对象
  • call()和apply()显式指定this的值,this的值为指定的值

两个特例

this值的确定除了由函数调用方式决定的通用法则外,还有两个特例:事件处理程序和定时器回调中的this值。

事件处理程序

事件处理程序中,this值为注册事件处理程序的元素对象,通过查看调用栈,事件处理程序是以函数的形式调用的,如下图所示。

事件处理程序的调用栈

从调用方式来看,this值应该是undefined,但是这在事件处理程序中没有意义,而this值是注册事件程序所在的元素对象才有实际意义,也是在实际编程中通常期望的值。

定时器中的「this」

在定时器(setTimeoutsetInterval)和requestAnimationFrame的回调中,无论是否使用严格模式,this值一律是global或者workerGlobal

这是不合理的,这个问题也引起了激烈的讨论

ES6中的「词法this」

ES6引入了箭头函数,它的一个重要特征是该函数没有this对象,而是通过词法作用域链获取this

「this」与作用域

对于普通函数而言,函数被调用时,在该函数的作用域中就会自动创建this对象,每个函数中都会有一个局部变量this,所以无法直接通过this标识符获取到外层函数的this值。

要获取外层函数中的this通常通过设置一个临时变量保存外层函数的this然后通过词法作用域获取,或者使用bind方法绑定外层函数的this

1
2
3
4
5
6
7
8
9
10
11
function outer() {
  const context = this;

  function inner() {
    // 可以使用context访问到outer中的this
  }

  innerBindThis = function() {
    // this为函数outer中的this
  }.bind(this);
}

但是this和所有其他标识符一样,遵循词法作用域规则。这也是ES6中的箭头函数没有创建this对象时访问到外层函数的this的原因。

绑定「this」

在实际编程中会遇到需要某个函数以特定的上下文运行的情景,这时可以使用函数的bind方法创建一个绑定了this对象的函数。

绑定this常在事件处理程序中使用。

1
2
3
4
5
6
7
8
9
10
const obj = {
  count: 0,
  increment() {
    this.count++;
  }
};
const btn = document.querySelector('.btn');

btn.addEventListener('click', obj.increment);    // => this为btn对象,出错
btn.addEventListener('click', obj.increment.bind(obj));    // => 绑定this为obj

小结

JavaScript中,this对象在函数被调用时自动创建,由函数的调用方式决定。但是有两个特例:事件处理程序中,this的值是注册事件程序的元素对象;定时器和requestAnimationFrame的回调中,this的值是globalworkerGlobal,无论是否使用严格模式。

this和任何标识符一样,遵循词法作用域规则,因为普通函数在被调用时会自动创建this对象,每个函数中都有一个局部变量this,所以无法直接通过外层函数中的this值。而ES6中的箭头函数不会创建this局部变量,可以直接获取到外层函数中的this

当需要访问外层函数中的this时,可以使用箭头函数、使用临时变量保存外层函数的this然后通过该临时变量访问或者使用bind方法绑定外层函数的this。绑定this还通常用于某个函数需要使用某个特定对象作为上下文的情况。

参考

“use strict” VS setTimeout

Arrow This