this是什么

this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。学习 this 的第一步是明白 this 既不指向函数自身也不指向函数的词法作用域。this 实际上是在函数调用时发生的绑定,它只想什么完全取决于函数在哪里被调用。

绑定规则

找到函数的调用位置后,判断需要应用下面四条规则中的那一条。首先会解释这四条规则,然后解释多条规则可用湿他们的优先级如何排列

默认绑定

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function foo() {
console.log(this.a);
}

var a = 2;

foo();// 2
```
当调用 foo() 时,this.a 被解析成了全局变量 a,因为在本例中,函数调用时应用了 this 的默认绑定,因此 this 指向全局对象。

要知道是否使用了默认绑定。可以通过查看 foo() 是如何调用的。在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式(strict mode),则不能将对象用于区默认绑定,this 会绑定到 undefined。
```js
function foo() {
"use strict";
console.log(this.a);
}

var a = 2;

foo();// TypeError: this is undefined

隐式绑定

隐式绑定需要考虑调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log(this.a)
}

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

obj.foo(); // 2

调用位置会使用 obj 上下文来引用函数,因此可以说函数被调用时 obj 对象“拥有”或者“包含”函数引用。当 foo() 被调用时,它的前面确实加上了对 obj 的引用。当函数引用有上下文对象时,隐式绑定会把函数调用中的 this 绑定到这个上下文对象。因为调用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo() {
console.log(this.a);
}

var obj2 = {
a: 42,
foo: foo
};

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

obj1.obj2.foo(); // 42

隐式丢失

一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是他会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。
思考以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this.a);
}

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

var bar = obj.foo; // 函数别名
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

虽然 bar 是 obj.foo 的一个引用,但是实际上它引用的是 foo 函数本身,因此此刻的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定
此外,回调函数有可能会丢失 this 绑定,可以通过固定 this 来解决这个问题

显式绑定

使用 call() 和 apply() 方法可以直接指定 this 的绑定对象,因此称为显式绑定

1
2
3
4
5
6
7
8
9
function foo() {
console.log(this.a);
}

var obj = {
a: 2;
}

foo.call(obj); // 2

通过 foo.call(…),可以在调用 foo 时将它的 this 强制绑定到 obj 上。
从 this 绑定的角度来说,call() 和 apply() 是一样的,区别在于 call() 的参数是依次传入的,apply() 的参数为在数组中一次性传入

new绑定

使用 new 来调用函数,会自动执行以下操作

  1. 创建一个全新的对象。
  2. 这个新对象会被执行 Prototype 指向。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
    1
    2
    3
    4
    5
    6
    function foo(a) {
    this.a = a;
    }

    var bar = new foo(2);
    console.log(bar.a);// 2
    使用 new 调用 foo 时,会构造一个新对象并把它绑定到 foo() 调用中的 this 上。

优先级

可以按照下面顺序来判断
从先到后分别为

  1. var bar = new foo() new 绑定
  2. var bar = foo.call(obj2) call()、apply() 显式绑定
  3. var bar = obj1.foo 在上下文对象中调用(隐式绑定)
  4. var bar = foo() 默认绑定