JavaScript中的this绑定

1. 调用位置

在理解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的调用位置

2. 四个规则

接下来我们来看看在函数的执行过程中调用位置如何决定this的绑定对象。 你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

1. 默认绑定

直接使用不带任何修饰的函数引用进行调用的,只能使用默认绑定,无法应用其他规则。

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

如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定 到 undefined。

function foo() {
"use strict";
console.log(this.a);
}
var a = 2;
foo(); // TypeError: this is undefined

2. 隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。当函数引 用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。

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

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

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() 其实是一个不带任何修饰的函数调用, 因此应用了默认绑定。

3. 显式绑定

JavaScript 提供的绝大多数函数以及你自 己创建的所有函数都可以使用 call(..) 和 apply(..) 方法。这两个方法是如何工作的呢?它们的第一个参数是一个对象,它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我们称之为显式绑定。

(1) 硬绑定

我们来看看这个变种到底是怎样工作的。我们创建了函数 bar(),并在它的内部手动调用 了 foo.call(obj),因此强制把 foo 的 this 绑定到了 obj。无论之后如何调用函数 bar,它 总会手动在 obj 上调用 foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。

 function foo() {
console.log( this.a );
}
 var obj = { a:2 };
 var bar = function() {
foo.call( obj );
};
 bar(); // 2
 setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的this
 bar.call( window ); // 2

(2) API调用的“ 上下文”

第三方库的许多函数, 以及 JavaScript 语言和宿主环境中许多新的内置函数, 都提供了一 个可选的参数, 通常被称为“ 上下文”(context), 其作用和 bind(..) 一样, 确保你的回调 函数使用指定的 this。

function foo(el) {
console.log(el, this.id);
}
var obj = {
id: “awesome”
};
// 调用 foo(..) 时把 this 绑定到 obj
[1, 2, 3].forEach(foo, obj); // 1 awesome 2 awesome 3 awesome

这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定, 这样你可以少些一些 代码。

4. new绑定

使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
 1. 创建(或者说构造)一个全新的对象。
 2. 这个新对象会被执行[[原型]]连接。
 3. 这个新对象会绑定到函数调用的this。
 4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

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

注意!

ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES6 之前代码中的 self = this 机制一样。

摘自《你所不知道的JavaScript》http://bit.ly/ydk-js-this-object-prototypes
A single golf clap? Or a long standing ovation?

By clapping more or less, you can signal to us which stories really stand out.