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

但它表现出同样的问题。

如何访问正确的对象?


924
2017-11-29 06:13


起源


我不时会厌倦某种问题,我决定写一个规范的答案。即使这些问题已被回答了一百万次,但并不总是能够找到一个好的问题+答案对,而这些问题没有被无关的信息“污染”。这是其中一个时刻和其中一个问题(我很无聊)。如果您认为这类问题确实存在一个良好的现有规范问题/答案,请告诉我,我将删除此问题。欢迎提出改进建议! - Felix Kling
有关: 在JavaScript原型函数中保留对“this”的引用 - Bergi
有关: 使用JavaScript原型对象时,事件方法中的“this”关键字 - Bergi
有用的TypeScript页面,也主要适用于JS。 - Ondra Žižka


答案:


你应该知道什么 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 特别是,但是 它所指的对象。这就是为什么一个简单的解决方案就是简单地创建一个也引用该对象的新变量。变量可以有任何名称,但常见的是 self 和 that

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

以来 self 是一个普通变量,它遵循词法范围规则,并且可以在回调中访问。这也有一个优点,你可以访问 this 回调本身的价值。

明确设定 this 回调 - 第1部分

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

每个函数都有这个方法 .bind  [文档],返回一个新函数 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.proxy  [文档] 代替。这样做的原因是,在解除对事件回调的绑定时,您不需要存储对该函数的引用。 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  [文档] 就是这样一种方法。它的签名是:

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 方法 [文档] 描述一个叫做的选项 context

此对象将成为所有与Ajax相关的回调的上下文。


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

此问题的另一个常见表现是将对象方法用作回调/事件处理程序。函数是JavaScript中的一等公民,术语“方法”只是一个函数的口语术语,它是对象属性的值。但是该函数没有与其“包含”对象的特定链接。

请考虑以下示例:

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 指的是取决于功能的方式 而不是它是怎么回事 定义
如果代码如下所示,则可能更明显的是该函数没有对该对象的隐式引用:

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


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

Foo.prototype.method = method;

解决方案 与上面提到的相同:如果可用,请使用 .bind 明确绑定 this 到一个特定的价值

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

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

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

或使用箭头功能:

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

1214
2017-11-29 06:13



菲利克斯,我之前读过这个答案,但从未回复过。我越来越担心人们会使用 self 和 that 参考 this。我觉得这样是因为 this 是在不同上下文中使用的重载变量;而 self 通常对应于本地实例和 that 通常是指另一个对象。我知道你没有设定这个规则,因为我看到它出现在其他许多地方,但这也是我开始使用的原因 _this但是我不确定别人的感受,除了已经产生的非统一做法。 - vol7ron
@FelixKling可以安全地假设使用这个内部原型函数将始终具有预期的行为,无论它们(通常)被调用的方式如何?在原型函数中使用回调时,是否有一个替代bind(),self或者? - andig
@FelixKling有时可以依赖它 Function.prototype.call () 和 Function.prototype.apply ()。特别是 apply () 我已经获得了很多里程。我不太倾向于使用 bind () 也许只是出于习惯,虽然我知道(但不确定)使用bind比其他选项可能有轻微的开销优势。 - Nolo
很好的答案,但考虑添加一个额外的可选解决方案,只是为了不使用类,新的或这一点。 - Aluan Haddad
重新箭头函数“相反,它在范围内被查找,就像一个普通变量。”完全为我点击了这个,谢谢! () => this.clicked() ;) - alphanumeric0101


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

  1. 您可以使用 捆绑() 功能。
  2. 将context / 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将对context / 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);
  });
}

141
2017-08-13 10:26





这一切都在调用方法的“神奇”语法中:

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

35
2018-05-21 00:11





“背景”的麻烦

术语“上下文”有时用于指代引用的对象 这个。它的使用是不合适的,因为它不适合在语义上或技术上 ECMAScript中的 这个

“上下文” 意味着围绕某些事物增加意义的情况,或者提供额外意义的一些前后信息。术语“上下文”在ECMAScript中用于指代 执行上下文,这是所有的参数,范围和 这个 在某些执行代码的范围内。

这显示在 ECMA-262第10.4.2节

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

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

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

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


20
2018-06-01 00:44





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

this & scope :


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

   1) Global Scope

   2) Function Scope

简而言之,全局范围是指窗口对象。在全局范围内声明的变量可以从任何地方访问。另一方面,函数范围驻留在函数内部。函数内部声明的变量通常无法从外部访问。this 全局范围中的关键字是指窗口对象。this inside函数也指window对象。所以 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 和四个方法叫 sayNameVersion1sayNameVersion2sayNameVersion3sayNameVersion4。它们中的所有四个都有一个特定的任务。接受回调并调用它。回调有一个特定的任务,即记录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)

}

现在让我们从person构造函数创建一个实例并调用不同版本的 sayNameVersionX (X指1,2,3,4)方法用 niceCallback 看看我们可以操纵多少种方式 this 内部回调引用 person 实例。

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

绑定: 

什么绑定做的是创建一个新的函数 this 关键字设置为提供的值。

sayNameVersion1 和 sayNameVersion2 使用bind来操纵 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

电话: 

first argument 的 call 方法用作 this 在使用的函数内部 call附在它上面。

sayNameVersion3 使用 call 操纵 this 引用我们创建的person对象,而不是window对象。

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

它被称为如下:

p1.sayNameVersion3(niceCallback)

申请: 

如同 call,第一个论点 apply 是指将由其表示的对象 this 关键词。

sayNameVersion4 使用 apply 操纵 this 引用人物对象

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

并且它被调用如下。简单地说回调是通过的,

p1.sayNameVersion4(niceCallback)

15
2017-08-18 17:58



任何有关答案的建设性批评将不胜感激! - AL-zami
全局范围中的this关键字不一定是指 窗口 目的。仅在浏览器中才是这样。 - Randall Flagg
@RandallFlagg我从浏览器的角度写了这个答案。如果有必要,可以免费提供这个答案:) - AL-zami


我们无法将其绑定到 setTimeout(),因为它总是执行 全局对象(窗口),如果你想访问 this 然后通过使用回调函数中的上下文 bind() 我们可以实现的回调函数:

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

10
2017-11-17 14:32



这与现有的任何答案有何不同? - Felix Kling