我有一个构造函数,注册了一个事件处理程序。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', function () {
alert(this.data);
});
}
// Mock transport object
var transport = {
on: function(event, callback) {
setTimeout(callback, 1000);
}
};
// called as
var obj = new MyConstructor('foo', transport);
然而,我无法在回调中访问所创建对象的data
属性。看起来this
并不是指被创建的对象,而是指另一个对象。
我还试图使用一个对象方法而不是一个匿名函数。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', this.alert);
}
MyConstructor.prototype.alert = function() {
alert(this.name);
};
但它表现出同样的问题。
我怎样才能访问正确的对象?
this
(又名"the context")是每个函数中的一个特殊的关键字,它的值只取决于如何调用该函数,而不是如何/何时/何地定义该函数。它不像其他变量那样受词义范围的影响(除了箭头函数,见下文)。下面是一些例子。
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
this
的信息,请看MDN文档。this
。实际上,你并不想特别访问this
,而是想访问它所指的*对象。这就是为什么一个简单的解决方案是简单地创建一个新的变量,同时也指代那个对象。这个变量可以有任何名字,但常见的有 "self "和 "that"。
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
由于self
是一个普通的变量,它遵守词法范围规则,可以在回调中访问。这也有一个好处,就是你可以访问回调本身的`this'值。
看起来你对this
的值没有控制权,因为它的值是自动设置的,但实际上不是这样的。
每个函数都有.bind
[docs]方法,它返回一个新的函数,this
被绑定到一个值。这个函数的行为与你调用.bind
的函数完全相同,只是this
是由你设置的。无论何时何地调用该函数,`this'都将指代传递的值。
function MyConstructor(data, transport) {
this.data = data;
var boundFunction = (function() { // parenthesis are not necessary
alert(this.data); // but might improve readability
}).bind(this); // <- here we are calling `.bind()`
transport.on('data', boundFunction);
}
在这种情况下,我们将回调的this'与
MyConstructor'的this'值绑定。 **注意:**当为jQuery绑定上下文时,使用[
jQuery.proxy` [docs]][proxy]代替。这样做的原因是,当解除事件回调时,你不需要存储对函数的引用,jQuery内部处理。
ECMAScript 6引入了箭头函数,它可以被认为是lambda函数。它们没有自己的this
绑定。取而代之的是,this'在作用域中被查找,就像一个普通的变量。这意味着你不需要调用
.bind'。这并不是它们唯一的特殊行为,请参考MDN文档以获得更多信息。
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
一些接受回调的函数/方法也接受一个回调的this'的值。这基本上和你自己绑定它是一样的,只是这个函数/方法为你做了这件事。[
Array#map` [docs]][map] 就是这样一个方法。它的签名是。
array.map(callback[, thisArg])
第一个参数是回调,第二个参数是 "this "应该引用的值。下面是一个假想的例子。
注意:你是否可以为this
传递一个值,通常在该函数/方法的文档中提到。例如,jQuery'的$.ajax
方法 [docs]描述了一个名为context
的选项。
这个对象将成为所有Ajax相关回调的上下文。
常见的问题:使用对象方法作为回调/事件处理程序
这个问题的另一个常见表现是当一个对象方法被用作回调/事件处理程序时。函数在JavaScript中是一等公民,术语"method"只是一个俗称,指的是作为对象属性值的一个函数。但是这个函数并没有与它的"包含"对象有特定联系。 考虑一下下面的例子。
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
函数this.method
被指定为点击事件处理程序,但如果document.body
被点击,记录的值将是undefined
,因为在事件处理程序里面,this
是指document.body
,而不是Foo
的实例。
正如开头已经提到的,this
指的是什么,取决于函数是如何被调用的,而不是如何被定义的。
如果代码像下面这样,可能就更明显了,这个函数没有隐式引用对象。
解决方案与上面提到的相同。如果可以的话,使用.bind
来明确地将this
绑定到一个特定的值上
document.body.onclick = this.method.bind(this);
或者通过使用匿名函数作为回调/事件处理程序,明确调用函数作为对象的"方法",并将对象(this
)分配给其他变量。
var self = this;
document.body.onclick = function() {
self.method();
};
或者使用一个箭头函数。
document.body.onclick = () => this.method();
这都是在调用方法的"魔法"语法中。
object.property();
当你从对象中获取属性并一次性调用它时,对象将成为该方法的上下文。如果你调用同一个方法,但分步进行,那么上下文就是全局范围(窗口)。
var f = object.property;
f();
当你获得一个方法的引用时,它就不再依附于对象,而只是一个普通函数的引用。当你获得作为回调使用的引用时,也会发生同样的情况。
this.saveNextLevelData(this.setAll);
这就是你要将上下文与函数绑定的地方。
this.saveNextLevelData(this.setAll.bind(this));
如果你使用jQuery,你应该使用$.proxy
方法,因为bind
在所有浏览器中都不支持。
this.saveNextLevelData($.proxy(this.setAll, this));
术语"context"有时被用来指代this所引用的对象。它的使用是不恰当的,因为它在语义上和技术上都不符合ECMAScript'的this。
"Context"是指增加意义的事物周围的环境,或者是赋予额外意义的一些前后的信息。术语"上下文"在ECMAScript中是指执行上下文,它是一些执行代码范围内的所有参数、范围和this。
这在ECMA-262第10.4.2节中有所体现。
将ThisBinding设置为与 调用执行环境的ThisBinding
这清楚地表明,这是执行上下文的一部分。
执行上下文提供了周围的信息,为正在执行的代码增加了意义。它包括比thisBinding多得多的信息。
因此,this的值并不是"上下文",它只是执行上下文的一部分。它本质上是一个局部变量,可以被调用者设置为任何对象,在严格模式下,可以设置为任何值。