如何在callback中访问正确的`this`?

我有一个构造函数注册一个事件处理程序:

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); 

但是,我无法访问callback中创build的对象的data属性。 看起来this不是指创build的对象,而是指向另一个对象。

我也尝试使用对象方法而不是匿名函数:

 function MyConstructor(data, transport) { this.data = data; transport.on('data', this.alert); } MyConstructor.prototype.alert = function() { alert(this.name); }; 

但它performance出同样的问题。

我如何访问正确的对象?

你应该知道this

this (又名“上下文”)是每个函数内的一个特殊的关键字,它的值只取决于函数的调用方式,而不是如何/何时/何处定义。 与其他variables一样,它不受词汇范围的影响。 这里有些例子:

 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` 

要了解更多信息,请查看MDN文档 。


如何参照正确的this

不要使用this

您实际上不想特别访问this ,而是它所指的对象 。 这就是为什么一个简单的解决scheme是简单地创build一个新的variables,也指向该对象。 variables可以有任何名称,但常见的是selfthat

 function MyConstructor(data, transport) { this.data = data; var self = this; transport.on('data', function() { alert(self.data); }); } 

由于self是一个正常的variables,它遵从词法范围规则,并且可以在callback中访问。 这也有你可以访问callback本身的this值的优点。

明确设置callback – 第1部分

它可能看起来像你无法控制这个值,因为它的值是自动设置的,但事实并非如此。

每个函数都有方法.bind [docs] ,它返回一个新的函数,并绑定到一个值。 该函数的行为与您所调用的.bind完全相同,只是由您设置的。 无论该函数何时或何时被调用, 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); } 

在这种情况下,我们将callback的this绑定到MyConstructorthis值。

注意:当为jQuery绑定上下文时,请改用jQuery.proxy [docs] 。 这样做的原因是,当解除绑定事件callback时,不需要存储对函数的引用。 jQuery在内部处理。

ECMAScript 6:使用箭头function

ECMASCript 6引入了箭头函数 ,它可以被认为是lambda函数。 他们没有自己的this绑定。 相反, this就像一个正常的variables一样在范围内查找。 这意味着你不必调用.bind 。 这不是他们唯一的特殊行为,请参阅MDN文档以获取更多信息。

 function MyConstructor(data, transport) { this.data = data; transport.on('data', () => alert(this.data)); } 

设置callback – 第2部分

一些接受callback的函数/方法也接受这个callback应该引用的值。 这与自己绑定的基本相同,但函数/方法为你做。 Array#map [docs]就是这样一种方法。 其签名是:

 array.map(callback[, thisArg]) 

第一个参数是callback,第二个参数是应该引用的值。 这是一个人为的例子:

 var arr = [1, 2, 3]; var obj = {multiplier: 42}; var new_arr = arr.map(function(v) { return v * this.multiplier; }, obj); // <- here we are passing `obj` as second argument 

注意:是否可以this传递一个值通常在该函数/方法的文档中提及。 例如, jQuery的$.ajax方法[docs]描述了一个名为context的选项:

这个对象将成为所有与Ajax相关的callback的上下文。


常见问题:使用对象方法作为callback/事件处理程序

这个问题的另一个常见的performance是当一个对象方法被用作callback/事件处理程序。 函数是JavaScript中的第一类公民,术语“方法”仅仅是一个对象属性值的函数的口语。 但是这个函数没有特定的链接到它的“包含”对象。

考虑下面的例子:

 function Foo() { this.data = 42, document.body.onclick = this.method; } Foo.prototype.method = function() { console.log(this.data); }; 

函数this.method被分配为单击事件处理程序,但是如果单击正文,logging的值将是undefined ,因为在事件处理程序中, this指的是正文,而不是Foo的实例。
正如开头已经提到的那样, this是指这个函数是如何被调用的 ,而不是如何定义的
如果代码如下所示,则可能更明显的是该函数没有对对象的隐式引用:

 function method() { console.log(this.data); } function Foo() { this.data = 42, document.body.onclick = this.method; } Foo.prototype.method = method; 

解决scheme与上面提到的相同:如果可用,请使用.bind将其显式绑定到特定的值

 document.body.onclick = this.method.bind(this); 

或通过使用具有callback/事件处理程序的匿名函数并将该对象( this )分配给另一个variables来明确地将该函数作为对象的“方法”来调用:

 var self = this; document.body.onclick = function() { self.method(); }; 

或使用箭头function:

 document.body.onclick = () => this.method(); 

这里有几种方法来访问子上下文中的父上下文 –

  1. 你可以使用bind()函数 – https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
  2. 将参考存储在另一个variables中
  3. 使用ES6箭头function – https://derickbailey.com/2015/09/28/do-es6-arrow-functions-really-solve-this-in-javascript/
  4. 修改代码/函数体系结构 – 为此,您应该使用JavaScript中的devise模式命令 – https://addyosmani.com/resources/essentialjsdesignpatterns/book/

1.使用bind()函数

 function MyConstructor(data, transport) { this.data = data; transport.on('data', ( function () { alert(this.data); }).bind(this) ); } // Mock transport object var transport = { on: function(event, callback) { setTimeout(callback, 1000); } }; // called as var obj = new MyConstructor('foo', transport); 

如果您正在使用underscore.jshttp://underscorejs.org/#bind

 transport.on('data', _.bind(function () { alert(this.data); }, this)); 

2将context / this的引用存储在另一个variables中

 function MyConstructor(data, transport) { var self = this; this.data = data; transport.on('data', function() { alert(self.data); }); } 

3个箭头function

 function MyConstructor(data, transport) { this.data = data; transport.on('data', () => { alert(this.data); }); } 

这一切都在调用方法的“魔术”语法中:

 object.property(); 

从对象获取属性并一次调用它时,该对象将成为该方法的上下文。 如果您调用相同的方法,但在单独的步骤中,上下文是全局范围(窗口),而不是:

 var f = object.property; f(); 

当你得到方法的引用时,它不再附加到对象,它只是一个普通函数的引用。 当你把这个引用用作callback函数的时候也是一样的:

 this.saveNextLevelData(this.setAll); 

这就是你将上下文绑定到函数的地方:

 this.saveNextLevelData(this.setAll.bind(this)); 

如果您使用jQuery,则应该使用$.proxy方法,因为所有浏览器都不支持bind

 this.saveNextLevelData($.proxy(this.setAll, this)); 

“背景”的麻烦

术语“上下文”有时用来指代由此引用的对象。 这是使用是不恰当的,因为它不符合语义或技术与ECMAScript的这一点

“语境”是指围绕增加意义的事物的情况,或者是一些给予额外含义的前后信息。 在ECMAScript中使用术语“上下文”来指代执行上下文 ,这是所有的参数,作用域和在一些执行代码范围内的这个

这在ECMA-262第10.4.2节中显示 :

将ThisBinding设置为与调用执行上下文的ThisBinding相同的值

这清楚地表明是执行上下文的一部分。

执行上下文提供周围的信息,为正在执行的代码添加含义。 它包含更多的信息,只是thisBinding

所以这个值不是“上下文”,它只是执行上下文的一部分。 它本质上是一个局部variables,可以通过调用任何对象并在严格模式下设置为任何值。

首先,您需要在“范围”的上下文中清楚地理解“this”关键字的“范围”和行为。

“这个”和“范围”:


 there are two types of scope in javascript. They are : 1) Global Scope 2) Function Scope 

简而言之,全局范围是指窗口对象。在全局上下文中声明的variables可以从任何地方访问。另一方面,函数范围驻留在一个函数内,在一个函数内声明的variables不能从外界正常访问。 全局范围内的“this”关键字指的是窗口对象。 这个“内部函数”也指窗口对象。所以“this”将总是指向窗口,直到我们find一种方法来操纵“this”来表示我们自己select的上下文。

 -------------------------------------------------------------------------------- - - - Global Scope - - ( globally "this" refers to window object) - - - - function outer_function(callback){ - - - - // outer function scope - - // inside outer function"this" keyword refers to window object - - - callback() // "this" inside callback also refers window object - - } - - - - function callback_function(){ - - - - // function to be passed as callback - - - - // here "THIS" refers to window object also - - - - } - - - - outer_function(callback_function) - - // invoke with callback - -------------------------------------------------------------------------------- 

举例来说,在callback中操纵THIS的不同方法:

在这里我有一个名为Person的构造函数。 它有一个名为"name"的属性和四个名为"sayNameVersion1""sayNameVersion2""sayNameVersion3""sayNameVersion4" 。 所有四个人都有一个特定的任务。接受callback并调用它。callback有一个特定的任务是loggingPerson构造函数实例的name属性。

 function Person(name){ this.name = name this.sayNameVersion1 = function(callback){ callback.bind(this)() } this.sayNameVersion2 = function(callback){ callback() } this.sayNameVersion3 = function(callback){ callback.call(this) } this.sayNameVersion4 = function(callback){ callback.apply(this) } } function niceCallback(){ // function to be used as callback var parentObject = this console.log(parentObject) } 

现在让我们从person构造函数中创build一个实例,并使用“niceCallback”调用不同版本的"sayNameVersionX"方法,以查看我们可以通过多less种方式操作“this”内部callback来引用person实例。

 var p1 = new Person('zami') // create an instance of Person constructor 

绑定:

绑定的方法是用“this”关键字设置为提供的值来创build一个新的函数。

sayNameVersion1sayNameVersion2使用绑定来操纵callback函数的“this”。

 this.sayNameVersion1 = function(callback){ callback.bind(this)() } this.sayNameVersion2 = function(callback){ callback() } 

第一个将“this”与方法本身内部的callback绑定在一起,第二个callback与绑定到它的对象一起传递。

 p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback 

电话:

“调用”方法的第一个参数是作为这个在调用“call”的函数中使用的。

sayNameVersion3使用调用来操纵“this”来引用我们创build的person对象,而不是window对象。

 this.sayNameVersion3 = function(callback){ callback.call(this) } 

它被称为如下:

 p1.sayNameVersion3(niceCallback) 

适用:

与“call”类似,apply的第一个参数指的是将被“this”关键字指示的对象。

sayNameVersion4使用apply来操作“this”来引用person对象

 this.sayNameVersion4 = function(callback){ callback.apply(this) } 

它被称为如下所示。简单的callback通过,

 p1.sayNameVersion4(niceCallback)