当一个函数被调用时,会创建一个执行上下文。这个上下文会包含函数的调用栈,函数的调用方式,传入参数等信息。this 就是这个上下文的一个属性,会在函数的执行过程中用到。

误解

1. 指向函数自身

人们容易把 this 理解成指向函数自身。

function foo(num) {
  this.count++;
}
foo.count = 0;
for (var i = 0; i < 5; i++) {
  foo(i);
}
foo.count; // 0
count; // NaN

执行 foo.count = 0 时,函数对象 foo 添加了一个属性 count。但是函数内部代码 this.count 中的 this 并不是指向那个函数对象,虽然属性名相同,根对象并不相同,代码中无意中创建了一个全局变量 count,他的值为 NaN。

可以使用 call(..) 确保 this 指向函数对象 foo 本身,foo.call(foo, i);

2. 函数的作用域

this 在任何情况下都不指向函数的词法作用域。

function foo() {
  var a = 2;
  this.bar();
}
function bar() {
  console.log(this.a);
}
foo(); // ReferenceError: a is not defined

代码试图通过 this.bar() 来引用 bar() 函数,这样调用成功纯属意外,调用 bar() 最自然的办法是省略前面的 this,直接使用词法引用标识符。这段代码还试图使用 this 联通 foo() 和 bar() 的词法作用域,从而让 bar() 可以访问 foo() 作用域里的变量 a,这是不可能的。

绑定规则

1. 默认绑定

在独立函数调用的时候,就会使用默认绑定,this 会指向全局对象。

如果使用严格模式,则不能讲全局对象用于默认绑定,因此 this 会绑定到 undefined

function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 2

2. 隐式绑定

判断调用位置是否有上下文对象

function foo() {
  console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo,
};
obj.foo(); // 2

我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把 this 间接(隐式)绑定到这个对象上。

3. 显式绑定

显示绑定主要是用到了 call(..) 和 apply(..) 方法。

4. new 绑定

在 JavaScript 中,构造函数只是一些使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,他们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[Prototype]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function foo(a) {
  this.a = a;
}
var bar = new foo(2);
bar.a; // 2

优先级

new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

箭头函数

箭头函数不会创建自己的 this ,它只会从自己的作用域链的上一层继承 this。所以箭头函数中的 this 是在定义函数的时候绑定,而不是在执行函数的时候绑定。

var foo = 'foo';
var bar = {
  foo: 'bar',
  log: () => {
    console.log(this.foo);
  },
};
bar.log(); // 'foo'

通过 https://babeljs.io/repl 编译过程,我们发现箭头函数会被转成 console.log((void 0).foo)

其他

setTimeout 中所执行函数中的 this,永远指向 window,严格模式指向 undefined。

题目

const obj = {
  msg: 'foo',
  getMsg() {
    const msg = 'bar',
    return this.msg;
  }
}

obj.getMsg();

obj.getMsg() 是一个方法调用,调用该方法的上下文是 obj,所有 this 是指 obj。 答案是foo,可以参考误解 1。

var length = 4;
function cb() {
  console.log(this.length);
}

const obj = {
  length: 5,
  method(cb) {
    cb();
  },
};

obj.method(cb, 1, 2);

cb() 是在 method() 内部使用常规函数调用来调用的。由于在常规函数调用期间的 this 值等于 window。答案是 4

var length = 4;
function cb() {
  console.log(this.length);
}

const obj = {
  length: 5,
  method() {
    arguments[0]();
  },
};

obj.method(cb, 1, 2);

obj.method(cb, 1, 2) 被调用时有 3 个参数。arguments[0]() 是 arguments 对象上的回调的方法调用,所以回调内部的参数等于 arguments。所以 callback() 中的 this.length 与 arguments.length 相同,答案是 3

参考链接