问题:如何在回调中访问正确的" 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);

但是,我无法在回调内部访问已创建对象的data属性。看起来this并不引用创建的对象,而是引用另一个对象。

我还尝试使用对象方法代替匿名函数:

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

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

但出现相同的问题。

如何访问正确的对象?

标签:javascript,callback,this

回答1:

您应该了解的this

this(又称"上下文")是每个函数中的特殊关键字,其值仅取决于函数调用的方式,而不取决于方法/时间/位置它是定义的。它不受其他变量之类的词法作用域的影响(箭头函数除外,请参见下文)。以下是一些示例:

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

您实际上并不想特别访问this,而是访问它所引用的对象。这就是为什么一个简单的解决方案是简单地创建一个也引用该对象的新变量。变量可以有任何名称,但常见的是selfthat

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

由于self是普通变量,因此它遵循词法作用域规则,并且可以在回调内部进行访问。这还有一个优点,就是您可以访问回调本身的this值。

明确设置回调的this-第1部分

您似乎无法控制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绑定到MyConstructorthis的值。

注意::为jQuery绑定上下文时,请使用 jQuery.代理 [docs] 。这样做的原因是,使您在取消绑定事件回调时不需要存储对该函数的引用。 jQuery在内部进行处理。

ECMAScript 6:使用箭头功能

ECMAScript 6引入了箭头功能,可以将其视为lambda函数。他们没有自己的this绑定。而是在范围内查找this,就像普通变量一样。这意味着您不必调用.bind。这不是它们唯一的特殊行为,有关更多信息,请参考MDN文档。

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

设置回调的this-第2部分

某些接受回调的函数/方法也接受回调的this应该引用的值。这基本上与您自己绑定它相同,但是函数/方法可以为您完成它。 Array#map < em> [docs] 是这样的方法。它的签名是:

array.map(callback[, thisArg])

第一个参数是回调,第二个参数是此this应该引用的值。这是一个人为的示例:

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相关的回调的上下文。


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

此问题的另一个常见表现是将对象方法用作回调/事件处理程序时。函数是JavaScript中的一等公民,术语"方法"仅是一个俗称的函数,即对象属性的值。但是该函数没有指向其"包含"对象的特定链接。

请考虑以下示例:

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

Foo.prototype.method = function() {
    console.log(this.data);
};

函数this.method被分配为click事件处理程序,但是如果单击document.body,则记录的值将为undefined,因为在事件处理程序中,此this是指document.body,而不是Foo的实例。
首先,所指的内容取决于该函数如何调用,而不是定义的方式。
在下面的内容中,更明显的是该函数没有对该对象的隐式引用:

function method() {
    console.log(this.data);
}


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

Foo.prototype.method = method;

解决方案与上述相同:如果可用,请使用.bindthis绑定到特定值

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

或通过使用匿名函数作为回调/事件处理程序,并将对象(this)分配给另一个变量,来显式调用该函数作为对象的"方法":

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

或使用箭头功能:

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

回答2:

以下是在子上下文中访问父上下文的几种方法-

  1. 您可以使用 绑定() 函数。
  2. 将对上下文/ this的引用存储在另一个变量中(请参见下面的示例)。
  3. 使用ES6 箭头函数。
  4. 更改代码/功能设计/体系结构-为此,您应该通过设计模式在javascript中。

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将对上下文/ this的引用存储在另一个变量中

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

3箭头功能

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

回答3:

这都是调用方法的"魔术"语法:

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

回答4:

"上下文"带来的麻烦

"上下文"一词有时用于指代 this 所引用的对象。它的使用是不合适的,因为它在语义上或技术上都不符合 ECMAScript的

"上下文" 表示围绕某些事物添加含义的环境,或一些先于和以下信息具有其他意义。 ECMAScript中使用的术语"上下文"指的是 执行上下文< / em> ,它是某些执行代码范围内的所有参数,范围和 this

这在 ECMA-262第10.4.2节中显示

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

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

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

所以 this 的值不是"上下文",它只是执行上下文的一部分。它本质上是一个局部变量,可以通过对任何对象的调用并在严格模式下将其设置为所有值。

回答5:

您应该了解"此"关键字。

根据我的观点,您可以通过三种方式实现"此" (自/箭头功能/绑定方法)

与其他语言相比,函数的this关键字在JavaScript中的行为略有不同。

严格模式和非严格模式也有一些区别。

在大多数情况下,此值取决于函数的调用方式。

在执行过程中不能通过赋值来设置它,每次调用该函数时可能会有所不同。

ES5引入了bind()方法来设置函数this的值,而不管其调用方式如何,

和ES2015引入了箭头函数,它们不提供自己的this绑定(它保留了封闭词法上下文的this值)。

方法1:自我-自我用于维护对原始内容的引用,即使上下文在变化。这是事件处理程序中经常使用的一种技术(尤其是在闭包中)。

参考 https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

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

Method2 :箭头函数-箭头函数表达式在语法上紧凑于常规函数表达式

尽管没有与this,arguments,super或new.target关键字的绑定。

箭头函数表达式不适合用作方法,不能用作构造函数。

参考 https ://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions

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

Method3 :绑定-bind()方法创建一个新函数,

在调用时,将其this关键字设置为提供的值

在调用新函数时提供的给定参数序列之前。

参考 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_objects/Function/bind

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

回答6:

首先,您需要清楚地了解 scope this 关键字在上下文中的行为 scope

thisscope


there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope
简而言之,

全局范围是指窗口对象。在全局范围内声明的变量可以从任何地方访问;另一方面,函数范围位于函数内部。在函数内部声明的变量通常无法从外部访问。全局范围内的 this 关键字指的是窗口对象。 this 内部函数也指的是窗口对象。 this 将始终引用该窗口,直到我们找到一种方法来操纵 this 来指示我们自己的上下文选择。

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

在回调函数中操作this的不同方法:

在这里,我有一个名为Person的构造函数。它具有一个名为 name 的属性和四个名为 sayNameVersion1 sayNameVersion2的方法> sayNameVersion3 sayNameVersion4 。它们全部有一个特定的任务。接受一个回调并调用它。回调具有一个特定的任务,即记录Person构造函数实例的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)

}

现在让我们从人员构造函数创建一个实例,并使用 sayNameVersionX (X表示1,2,3,4)方法> niceCallback 来查看我们有多少种方法可以操纵回调中的 this 来引用 person 实例。

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

绑定:

绑定的作用是使用 this 关键字设置为提供的值来创建新功能。

sayNameVersion1sayNameVersion2使用绑定操作回调函数的 this

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

第一个将 this 与方法本身内部的回调绑定。第二个回调与绑定的对象一起传递。

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 方法的 第一个参数 用作 this call 的函数内部调用code>

sayNameVersion3使用 call 来操纵 this 来指代此人我们创建的对象,而不是窗口对象。

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

它的名称如下:

p1.sayNameVersion3(niceCallback)

应用:

类似于 call apply 的第一个参数是指将由 关键字。

sayNameVersion4使用 apply 来操纵 this 来引用人对象< / p>

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

它的调用方式如下所示:只需传递回调,

p1.sayNameVersion4(niceCallback)

回答7:

我们不能将其绑定到setTimeout(),因为如果要访问this全局对象(Window)一起执行.>回调函数中的上下文,然后对回调函数使用bind()可以实现为:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);

回答8:

问题围绕着this关键字在javascript中的行为。 的行为如下所示,

  1. this的值通常由函数执行上下文确定。
  2. 在全局范围内,this引用全局对象(window对象)。
  3. 如果为任何功能启用了严格模式,则this的值将为undefined,就像在严格模式下一样,全局对象引用了undefined代替window对象。
  4. 此关键字将绑定到点之前的对象。
  5. 我们可以使用call()bind()apply()
  6. 显式设置此值
  7. 使用new关键字(构造函数)时,该关键字将绑定到正在创建的新对象上。
  8. 箭头功能不会绑定this-代替地,this是按词法绑定的(即基于原始上下文)

正如大多数答案所暗示的,我们可以使用箭头函数或bind()方法或Self 变量。我会引用 Google JavaScript样式中有关lambdas(箭头功能)的观点指南

相对于f.bind(this),尤其是goog.bind(f,this),优选使用箭头函数。避免编写const self = this。箭头函数对回调尤其有用,该回调有时会传递意外的附加参数。

Google显然建议使用lambda而不是bind或constself=this

所以最好的解决方案是使用如下所示的lambda,

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

参考:

  1. https://medium.com/tech-tajawal/javascript- this-4-rules-7354abdb274c
  2. arrow-functions-vs-bind

回答9:

如果在代码中使用类,则当前还有另一种方法。

类字段的支持下,可以通过以下方式实现:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

可以肯定的是,所有绑定上下文的都是老式的好箭头功能,但是以这种形式,它看起来比显式绑定更清晰。

由于它是第3阶段的提案,因此您将需要babel和适当的 babel插件< / a>立即进行处理(2018年8月8日)。

回答10:

另一种方法,这是DOM2以来在事件监听器中绑定this的标准方法,让您始终删除监听器(除其他好处外)是EventListener界面中的handleEvent(evt)方法:

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

有关使用handleEvent的详细信息,可在以下位置找到: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

回到顶部