如何在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可以有任何名称,但常见的是self
和that
。
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
绑定到MyConstructor
的this
值。
注意:当为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();
这里有几种方法来访问子上下文中的父上下文 –
- 你可以使用
bind()
函数 – https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind - 将参考存储在另一个variables中
- 使用ES6箭头function – https://derickbailey.com/2015/09/28/do-es6-arrow-functions-really-solve-this-in-javascript/
- 修改代码/函数体系结构 – 为此,您应该使用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.js
– http://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一个新的函数。
sayNameVersion1
和sayNameVersion2
使用绑定来操纵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)