首页 文章

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

提问于
浏览
1024

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

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

但它表现出同样的问题 .

如何访问正确的对象?

8 回答

  • 2

    你应该了解这一点

    this (又名"the context")是每个函数中的一个特殊关键字,其值仅取决于函数的调用方式,而不取决于函数的定义方式/时间/位置 . 它不像其他变量那样受词法范围的影响(箭头函数除外,见下文) . 这里有些例子:

    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 ,而是它所引用的对象 . 这就是为什么一个简单的解决方案就是简单地创建一个也引用该对象的新变量 . 变量可以有任何名称,但常见的是 selfthat .

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

    由于 self 是一个普通变量,它遵循词法范围规则并且可以在回调中访问 . 这也有一个优点,您可以访问回调本身的 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 的值 .

    Note: 绑定jQuery的上下文时,请改用jQuery.proxy [docs] . 这样做的原因是,在解除对事件回调的绑定时,您不需要存储对该函数的引用 . jQuery在内部处理 .

    ECMAScript 6:使用箭头功能

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

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

    设置回调 - 第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
    

    Note: 是否可以传递 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 被指定为单击事件处理程序,但如果单击 document.body ,则记录的值将为 undefined ,因为在事件处理程序中, this 指的是 document.body ,而不是 Foo 的实例 .
    正如开头已经提到的, this 指的是取决于函数是如何 called ,而不是它是如何 defined .
    如果代码如下所示,则可能更明显的是该函数没有对该对象的隐式引用:

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

    The solution 与上面提到的相同:如果可用,使用 .bindthis 显式绑定到特定值

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

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

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

    或使用箭头功能:

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

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

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

  • 0

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

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

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

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

    另一种方法是 the standard way since DOM2 在事件监听器中绑定 thislet you always remove the listener (以及其他好处)是 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

  • 21

    “上下文”的麻烦

    术语"context"有时用于指代由此引用的对象 . 它的使用是不合适的,因为它在语义上或技术上都不适合ECMAScript's this .

    "Context"表示围绕某些事物增加意义的情况,或者提供额外含义的一些前后信息 . 术语"context"在ECMAScript中用于引用execution context,这是所有参数,范围和这在一些执行代码的范围内 .

    这显示在ECMA-262 section 10.4.2中:

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

    这清楚地表明这是执行环境的一部分 .

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

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

  • 39

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

    • 您可以使用 bind() 功能 .

    • 在另一个变量中存储对context / this的引用(参见下面的示例) .

    • 使用ES6 Arrow功能 .

    • 更改代码/功能设计/体系结构 - 为此,您应该在javascript中使用design patterns命令 .

    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);
      });
    }
    
  • 14

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

    this & scope :


    there are two types of scope in javascript. They are :
    
       1) Global Scope
    
       2) Function Scope
    

    简而言之,全局范围是指窗口对象 . 在全局范围内声明的变量可以从任何地方访问 . 另一方面,函数范围驻留在函数内部 . 函数内部声明的变量通常无法从外部访问 . 全局范围内的 this 关键字是指窗口对象 . this inside函数也指窗口对象 . 所以 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                                              -
    --------------------------------------------------------------------------------
    

    Different ways to manipulate this inside callback functions:

    这里我有一个名为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)
    
    }
    

    现在让我们从人员构造函数创建一个实例,并使用 niceCallback 调用 sayNameVersionX (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 :

    call 方法的 first argument 在函数中用作 this ,该函数在附加 call 的情况下调用 .

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

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

    它被称为如下:

    p1.sayNameVersion3(niceCallback)
    

    apply :

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

    sayNameVersion4 使用 apply 来操纵 this 来引用person对象

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

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

    p1.sayNameVersion4(niceCallback)
    

相关问题