JS中This的详解

从绑定This的四种方式来全面解析JS中的This。
资料来源于You Don’t Know JS系列丛书
感谢作者的开源以及YDKJS的中文翻译

默认绑定(Default Binding)

第一种This绑定规则可以认为是没有其他规则适用时的默认规则。考虑下面这个代码段:

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

  1. 独立函数的调用,无其他规则适用,所以This被绑定到全局对象。
  2. 要注意的是默认绑定在strict模式下是不合法的。此时的This将会返回undefined
  3. 如果函数内容没有在严格模式中运行,即使他在严格模式的上下文中被调用,this依旧能返回全局对象。(当然,应该尽量避免使用严格和非严格模式混合)

隐含绑定(Implicit Binding)

当函数调用点有一个环境对象(context object),也称为拥有者(owning)或容器(containing)对象
则他将被隐含绑定到该对象。考虑下面这段代码:

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

当他是被这样调用时:obj1.obj2.foo();
只有obj2时影响调用点的。

隐含绑定的丢失

当一个隐含绑定丢失,则他将会退回默认绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()就等同于foo()。显然符合默认绑定
考虑回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// `fn` 只不过`foo`的另一个引用
fn(); // <-- 调用点!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a`也是一个全局对象的属性
doFoo( obj.foo ); // "oops, global"

与上面一样,fn知识foo的一个引用。
而下面的明确绑定将解决隐含绑定的丢失。

明确绑定(Explicit Binding)

很简单,就是通过call,apply,bind方法将this绑定到某一个环境上下文中
其中call、apply类似,他们将会立刻调用函数,而bind不会。

new绑定(new Binding)

考虑下面的代码:

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。 new是函数调用可以绑定this的最后一种方式,我们称之为 new绑定(new binding)。

四种绑定的顺序

bind绑定高于new,明确绑定高于隐含绑定高于默认绑定

判定this

  1. 函数是和new一起被调用的吗(new绑定)?如果是,this就是新构建的对象。
    var bar = new foo()
  2. 函数是用call或apply被调用(明确绑定),甚至是隐藏在bind 硬绑定 之中吗?如果是,this就是明确指定的对象。
    var bar = foo.call( obj2 )
  3. 函数是用环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,this就是那个环境对象。
    var bar = obj1.foo()
  4. 否则,使用默认的this(默认绑定)。如果在strict mode下,就是undefined,否则是global对象。
    var bar = foo()