this 的绑定规则


1、关于 this 的误解

  • this 指向函数自身。以下代码试图用 this 指向函数自身的属性来记录调用次数,然而这行不通:
function foo(num) {
  console.log("foo: " + num);
  this.count++;
}
foo.count = 0;

for (let i = 0; i < 10; i++) {
    foo(i);
}

console.log(foo.count);
  • this 指向函数的作用域。以下代码试图跨越边界,用 this 引用函数的词法作用域,但会抛出 TypeError:
function foo() {
  var a = 2;
  this.bar();
}

function bar() {
  console.log(this.a);
}

foo();

2、this 的调用位置

调用位置就是函数在代码中被调用的位置。要分析 this 的引用到底是什么关心的是调用位置就在当前正在执行的函数的一个调用

function baz() {
  // 当前调用栈是 baz
  // 当前的调用位置是全局作用域
  console.log("baz");
  bar(); // <- bar 的调用位置
}

function bar() {
  // 当前调用栈是 baz->bar
  // 当前调用位置在 baz 中
  console.log("bar");
  foo(); // <-foo 的调用位置
}

function foo() {
  // 当前调用栈是 baz->bar->foo
  // 当前调用位置在 bar 中
  console.log("foo");
}

baz(); // <-baz 的调用位置

3、绑定规则

(1)默认绑定

独立函数被直接调用时,this 默认绑定的就是全局作用域

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

var a = 2;
foo(); // 2

严格模式下无法使用默认绑定,this 会绑定到 undefined

(2)隐式绑定

在调用位置是否有上下文对象,或者是否被某个对象拥有或包含:

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

var obj = {
  a: 2,
  foo: foo
};

obj.foo(); // 2

以上代码中,foo 声明之后被作为引用属性添加到 obj 中,调用时会隐式使用 obj 作为上下文。

(3)显式绑定
  • 显式使用 call()apply() 函数,调用函数时,会把 this 绑定到指定对象上:
function foo() {
  console.log(this.a);
}

var obj = {
  a: 2
};

foo.call(obj); // 绑定到 obj
  • 使用 Function.prototype.bind,还能解决绑定丢失问题:
this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 返回 81

var retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域

var boundGetX = retrieveX.bind(module); // 将"this"绑定到module对象
boundGetX(); // 返回 81
(4)new 绑定
  • 构造函数只是一些使用 new 操作符时被调用的函数。
  • 构造函数创建的新对象会绑定到函数的 this。
function foo(a) {
  this.a = a;
}

var bar = new foo(2);
console.log(bar.a); // 2