How to access the correct `this` inside a callback?
我有一个注册事件处理程序的构造函数函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); |
但是,我无法访问回调中已创建对象的
我还尝试使用对象方法而不是匿名函数:
1 2 3 4 5 6 7 8 | function MyConstructor(data, transport) { this.data = data; transport.on('data', this.alert); } MyConstructor.prototype.alert = function() { alert(this.name); }; |
但它也有同样的问题。
如何访问正确的对象?
关于EDCOX1的0个方面你应该知道的
EDOCX1×0(AKA"上下文")是每个函数内部的一个特殊关键字,其值只取决于函数是如何调用的,而不是如何/何时/何地定义的。它不受词汇范围的影响,如其他变量(除了箭头函数,见下文)。下面是一些例子:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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` |
要了解有关
您实际上并不想访问
1 2 3 4 5 6 7 | function MyConstructor(data, transport) { this.data = data; var self = this; transport.on('data', function() { alert(self.data); }); } |
由于
您可能看起来无法控制EDOCX1的值(0),因为它的值是自动设置的,但事实并非如此。好的。
每个函数都有方法
1 2 3 4 5 6 7 | 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); } |
在这种情况下,我们将回调的
注意:jquery绑定上下文时,使用
ECMAScript 6引入了箭头函数,可以将其视为lambda函数。他们没有自己的
1 2 3 4 | function MyConstructor(data, transport) { this.data = data; transport.on('data', () => alert(this.data)); } |
设置回调的
一些接受回调的函数/方法也接受回调的
1 | array.map(callback[, thisArg]) |
第一个参数是回调,第二个参数是
1 2 3 4 5 6 | 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 |
注意:该函数/方法的文档中通常会提到是否可以传递EDOCX1的值(0)。例如,jquery的
This object will be made the context of all Ajax-related callbacks.
Ok.
常见问题:将对象方法用作回调/事件处理程序
这个问题的另一个常见表现是当一个对象方法被用作回调/事件处理程序时。函数是JavaScript中的第一类公民,"方法"一词只是对象属性值函数的俗语术语。但该函数没有指向其"包含"对象的特定链接。好的。
请考虑以下示例:好的。
1 2 3 4 5 6 7 8 | function Foo() { this.data = 42, document.body.onclick = this.method; } Foo.prototype.method = function() { console.log(this.data); }; |
函数
1 2 3 4 5 6 7 8 9 10 11 | function method() { console.log(this.data); } function Foo() { this.data = 42, document.body.onclick = this.method; } Foo.prototype.method = method; |
解决方法与上述相同:如果可用,使用
1 | document.body.onclick = this.method.bind(this); |
或者通过使用匿名函数作为回调/事件处理程序显式调用函数作为对象的"方法",并将对象(
1 2 3 4 | var self = this; document.body.onclick = function() { self.method(); }; |
或使用箭头功能:好的。
1 | document.body.onclick = () => this.method(); |
好啊。
下面是访问子上下文中父上下文的几种方法-
1。使用EDCOX1·2函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); |
如果您使用的是
1 2 3 | transport.on('data', _.bind(function () { alert(this.data); }, this)); |
2在另一个变量中存储对context/this的引用
1 2 3 4 5 6 7 | function MyConstructor(data, transport) { var self = this; this.data = data; transport.on('data', function() { alert(self.data); }); } |
3箭头功能
1 2 3 4 5 6 | function MyConstructor(data, transport) { this.data = data; transport.on('data', () => { alert(this.data); }); } |
这都是调用方法的"魔力"语法:
1 | object.property(); |
当您从对象中获取属性并一次性调用它时,该对象将成为该方法的上下文。如果调用相同的方法,但在不同的步骤中,上下文是全局范围(窗口):
1 2 | var f = object.property; f(); |
当您得到一个方法的引用时,它不再附加到对象上,它只是对一个普通函数的引用。当您得到用作回调的引用时,也会发生同样的情况:
1 | this.saveNextLevelData(this.setAll); |
这就是将上下文绑定到函数的地方:
1 | this.saveNextLevelData(this.setAll.bind(this)); |
如果使用jquery,则应使用
1 | this.saveNextLevelData($.proxy(this.setAll, this)); |
"语境"的问题
术语"上下文"有时用来指代此引用的对象。它的使用是不恰当的,因为它既不符合语义,也不符合ecmascript的技术要求。
"上下文"是指围绕增加含义的事物的环境,或提供额外含义的某些前后信息。ecmascript中的术语"context"是指执行上下文,它是一些执行代码范围内的所有参数、范围和这一点。
如ECMA-262第10.4.2节所示:
Set the ThisBinding to the same value as the ThisBinding of the
calling execution context
它清楚地表明这是执行上下文的一部分。
执行上下文提供了周围的信息,这些信息为正在执行的代码添加了意义。它包含更多的信息,仅此绑定。
所以这个值不是"上下文",它只是执行上下文的一部分。它本质上是一个局部变量,可以通过调用任何对象并在严格模式下设置为任何值。
首先,您需要清楚地了解EDOCX1·0和EDOCX1·1 }关键字在EDCOX1×0的上下文中的行为。
1 2 3 4 5 | there are two types of scope in javascript. They are : 1) Global Scope 2) Function Scope |
总之,全局范围是指窗口对象。在全局范围内声明的变量可以从任何地方访问。另一方面,函数范围驻留在一个函数中。函数内声明的变量通常不能从外部访问。全局范围内的EDCOX1×1×关键字是指窗口对象。函数也称为窗口对象。因此,EDCOX1〔1〕总是指向窗口,直到我们找到一种方法来操纵EDCOX1×1表示我们自己选择的上下文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | -------------------------------------------------------------------------------- - - - 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 - -------------------------------------------------------------------------------- |
在回调函数内部操作
这里我有一个名为person的构造函数函数。它有一个属性叫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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构造函数创建一个实例,并用
1 | var p1 = new Person('zami') // create an instance of Person constructor |
绑定:
bind的作用是创建一个新函数,并将
1 2 3 4 5 6 | this.sayNameVersion1 = function(callback){ callback.bind(this)() } this.sayNameVersion2 = function(callback){ callback() } |
第一个绑定
1 2 3 | p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback |
呼叫:
1 2 3 | this.sayNameVersion3 = function(callback){ callback.call(this) } |
它被称为如下:
1 | p1.sayNameVersion3(niceCallback) |
适用:
与
1 2 3 | this.sayNameVersion4 = function(callback){ callback.apply(this) } |
它的调用方式如下:只需传递回调,
1 | p1.sayNameVersion4(niceCallback) |
我们不能把它绑定到
1 2 3 | setTimeout(function(){ this.methodName(); }.bind(this), 2000); |
你应该知道"this"关键字。
根据我的观点,你可以用三种方式来实现"这个"(self/arrow函数/bind方法)
函数的this关键字在javascript中的行为与其他语言略有不同。
严格模式与非严格模式也存在一定差异。
在大多数情况下,这个值由函数的调用方式决定。
它不能在执行期间通过赋值来设置,并且每次调用函数时它可能会有所不同。
ES5引入了bind()方法来设置函数this的值,不管它是如何调用的,
ES2015引入了arrow函数,这些函数不提供自己的这个绑定(它保留了封闭词汇上下文的这个值)。
方法1:在上下文发生变化的情况下,self-self被用来维护对原始文档的引用。这是一种常用于事件处理程序(尤其是闭包)的技术。
参考:https://developer.mozilla.org/en-us/docs/web/javascript/reference/operators/this
1 2 3 4 5 6 7 | function MyConstructor(data, transport) { this.data = data; var self = this; transport.on('data', function () { alert(self.data); }); } |
方法2:arrow函数-arrow函数表达式是正则函数表达式的一种语法紧凑的替代方法,
尽管没有自己的绑定到this、arguments、super或new.target关键字。
箭头函数表达式不适合用作方法,不能用作构造函数。
参考文献:HTTPS://EXPLA.MZILLA.OG/EN-U/DOCS/Web/JavaScript /引用/功能/ ARROWIL函数
1 2 3 4 5 6 | function MyConstructor(data, transport) { this.data = data; transport.on('data',()=> { alert(this.data); }); } |
方法3:绑定-bdId()方法创建一个新函数,
调用时,将其关键字设置为所提供的值,
在调用新函数之前提供的任何给定序列的参数。
参考:https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/function/bind
1 2 3 4 5 | function MyConstructor(data, transport) { this.data = data; transport.on('data',(function() { alert(this.data); }).bind(this); |
目前,如果在代码中使用类,还有另一种方法是可能的。
有了类字段的支持,下一步可以做到:
1 2 3 4 5 6 7 | class someView { onSomeInputKeyUp = (event) => { console.log(this); // this refers to correct value // .... someInitMethod() { //... someInput.addEventListener('input', this.onSomeInputKeyUp) |
当然,在引擎盖下,绑定上下文的都是旧的好的箭头函数,但是在这种形式下,显式绑定看起来更清晰。
由于这是第3阶段的提案,所以您需要Babel和适当的Babel插件来处理它(2018年8月)。
另一种方法是
1 2 3 4 5 6 7 8 | var obj = { handleEvent(e) { // always true console.log(this === obj); } }; document.body.addEventListener('click', obj); |
有关使用
这个问题围绕着
正如大多数答案所表明的,我们可以使用arrow函数或
Prefer using arrow functions over f.bind(this), and especially over
goog.bind(f, this). Avoid writing const self = this. Arrow functions
are particularly useful for callbacks, which sometimes pass unexpected
additional arguments.
谷歌明确建议使用lambda而不是bind或
所以最好的解决办法是使用兰姆达斯,如下所示:
1 2 3 4 5 6 | function MyConstructor(data, transport) { this.data = data; transport.on('data', () => { alert(this.data); }); } |
参考文献: