this

绑定规则

找到调用位置后,我们还需根据绑定规则确定 this 的绑定对象。

默认绑定

最常用的函数调用类型:独立函数调用。无法应用其他规则的默认规则。

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

在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用 默认绑定,无法应用其他规则。
如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定 到 undefined;严格模式下与 foo() 的调用位置无关:

1
2
3
4
5
6
7
8
9
function foo() {
console.log(this.a);
}
var a = 2;
(function() {
"use strict";
foo(); // 2
})();
//不过在代码中混用严格模式和非严格模式的行为是不可取的。

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,不过这种说法可能会造成一些误导。

1
2
3
4
5
6
7
8
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
obj.foo();

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

隐式丢失

指的是被隐式绑定的函数丢失绑定对象,而应用默认绑定的情形。而默认绑定把 this 绑定到全局对象或者 undifined 上,取决于是否为严格模式。

1
2
3
4
5
6
7
8
9
10
11
function (){
console.log(this.a);
}
var obj = {
a:2;
foo:foo;
}
var bar = obj.foo; //函数别名
var a ="global"

bar(); //"global"

虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

显式绑定

如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?可以使用函数的 call(..) 和 apply(..) 方法。

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

通过 foo.call(..),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。

硬绑定

显式绑定仍然无法解决我们之前提出的丢失绑定问题。(硬绑定例子对比那隐式丢失的例子)。但是显式绑定的一个变种可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
var bar = function() {
foo.call(obj);
};

bar(); //2

//硬绑定bar不能修改foo的this指向了
bar.call(window); //2

由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype. bind,它的用法如下:

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

var obj = {
a:2
};
var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3 console.log( b ); // 5

bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。

new 绑定

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

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

使用 new 来调用 foo(..) 时,我们会构造一个新对象(_这个新对象名为 bar_)并把它绑定到 foo(..) 调用中的 this 上。

绑定规则的优先级

当两种绑定同时出现时,我们则需要比较它们的优先级

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

obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2
//可以看到,显式绑定优先级更高
  1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
    var bar = new foo()
  2. 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。
    var bar = foo.call(obj2)
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。
    var bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到 全局对象。
    var bar = foo()

箭头函数的 this 指向

箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo(){
return (a) => {
console.log(this.a);
};
}
var obj1 ={
a:2;
}
var obj2 ={
a:3;
}

var bar = foo.call(obj);
bar.call(obj2); //2

foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不 行!)

箭头函数可以像 bind(..) 一样确保函数的 this 被绑定到指定对象,此外,其重要性还体 现在它用更常见的词法作用域取代了传统的 this 机制。实际上,在 ES6 之前我们就已经 在使用一种几乎和箭头函数完全一样的模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function foo() {
setTimeout(() => {
// 这里的 this 在此法上继承自 foo()
console.log( this.a );
},100);
}
var obj = {
a:2
};
foo.call( obj ); // 2

function foo() {
var self = this; // lexical capture of this
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
foo.call( obj ); // 2

虽然 self = this 和箭头函数看起来都可以取代 bind(..),但是从本质上来说,它们想替 代的是 this 机制。
如果你经常编写 this 风格的代码,但是绝大部分时候都会使用 self = this 或者箭头函数 来否定 this 机制,那你或许应当:

  1. 只使用词法作用域并完全抛弃错误this风格的代码;
  2. 完全采用 this 风格,在必要时使用 bind(..),尽量避免使用 self = this 和箭头函数.
0%