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

问题描述 投票:1209回答:10

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

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
10个回答
1590
投票

你应该了解什么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 documentation


如何参考正确的this

Don't use this

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

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

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

Explicitly set this of the callback - part 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.proxy [docs]。这样做的原因是,在解除对事件回调的绑定时,您不需要存储对该函数的引用。 jQuery在内部处理。

ECMAScript 6: Use arrow functions

ECMAScript 6引入了箭头函数,可以将其视为lambda函数。他们没有自己的this绑定。相反,this就像普通变量一样在范围内查找。这意味着你不必打电话给.bind。这不是他们唯一的特殊行为,请参阅MDN文档以获取更多信息。

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

Set this of the callback - part 2

一些接受回调的函数/方法也接受回调的this应该引用的值。这与自己绑定基本相同,但函数/方法为您完成。 Array#map [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's $.ajax method [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的实例。 正如开头已经提到的那样,this所指的内容取决于函数的调用方式,而不是函数的定义方式。 如果代码如下所示,则可能更明显的是该函数没有对该对象的隐式引用:

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

另一种方法是自DOM2在事件监听器中绑定this以来总是删除监听器(以及其他好处)的标准方法,是来自handleEvent(evt)接口的EventListenermethod:

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


184
投票

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

  1. 你可以使用bind()功能。
  2. 将context / this的引用存储在另一个变量中(参见下面的示例)。
  3. 使用ES6 Arrow函数。
  4. 改变代码/功能设计/架构 - 为此你应该在javascript中命令design patterns

1. Use bind() function

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 Store reference to context/this inside another variable

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

3 Arrow function

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

44
投票

这都是调用方法的“神奇”语法:

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

23
投票

The trouble with "context"

术语“上下文”有时用于指代由此引用的对象。它的使用是不合适的,因为它不适合在语义上或技术上与ECMAScript's this

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

这在ECMA-262 section 10.4.2中显示:

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

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

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

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


19
投票

首先,您需要在scope的背景下清楚地了解thisscope关键字的行为。

thisscope


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

   1) Global Scope

   2) Function Scope

简而言之,全局范围是指窗口对象。在全局范围内声明的变量可以从任何地方访问。另一方面,函数范围驻留在函数内部。函数内部声明的变量通常无法从外部世界访问.this关键字in全局范围是指窗口object.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的属性和四种方法叫做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)

}

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

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

bind :

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

sayNameVersion1sayNameVersion2使用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

call :

first argument方法的call在函数内部用作this,该函数与附加的call一起调用。

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

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

它被称为如下:

p1.sayNameVersion3(niceCallback)

apply :

call类似,apply的第一个参数是指由this关键字指示的对象。

sayNameVersion4使用apply操纵this来指人物

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

它被调用如下。简单地说回调,

p1.sayNameVersion4(niceCallback)

15
投票

我们不能将它绑定到setTimeout(),因为它总是用全局对象(Window)执行,如果你想在回调函数中访问this上下文然后使用bind()来回调函数,我们可以实现:

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

15
投票

你应该知道“这个”关键字。

根据我的观点,你可以用三种方式实现“this”(自我/箭头功能/绑定方法)

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

它在严格模式和非严格模式之间也有一些区别。

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

它不能在执行期间通过赋值设置,并且每次调用函数时它可能不同。

ES5引入了bind()方法来设置函数的值,无论它如何被调用,

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

方法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);
    });
}

方法2:箭头函数 - 箭头函数表达式是常规函数表达式的语法紧凑替代方法,

虽然没有自己绑定到this,arguments,super或new.target关键字。

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

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

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

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

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

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

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

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

3
投票

目前,如果在代码中使用类,则可以采用另一种方法。

class fields的支持下,它可以通过下一个方式实现:

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

确实在幕后它是绑定上下文的所有旧的好箭头函数,但在这种形式下,它显然更明确的显式绑定。

因为它是第3阶段提案,所以你需要babel和适当的babel plugin来处理它(08/2018)。


3
投票

问题围绕this关键字在javascript中的行为方式。 this的行为如下,

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

正如大多数答案所示,我们可以使用箭头函数或bind()方法或Self var。我会引用Google JavaScript Style Guide关于lambdas(箭头函数)的观点

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

Google明确建议使用lambdas而不是bind或const self = this

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

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
© www.soinside.com 2019 - 2024. All rights reserved.