关于javascript:如何在回调中访问正确的”this”?

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

但是,我无法访问回调中已创建对象的data属性。看起来this不是指创建的对象,而是指另一个对象。

我还尝试使用对象方法而不是匿名函数:

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`

要了解有关this的更多信息,请查看MDN文档。好的。如何引用正确的this。不要使用this

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

1
2
3
4
5
6
7
function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

由于self是一个普通变量,所以它遵循词汇范围规则,并且在回调中可以访问。这还有一个优点,即您可以访问回调本身的this值。好的。显式设置回调的this—第1部分

您可能看起来无法控制EDOCX1的值(0),因为它的值是自动设置的,但事实并非如此。好的。

每个函数都有方法.binddocs〕,它返回一个新函数,其中this绑定到一个值。该函数的行为与您在上调用.bind时的行为完全相同,只是this是由您设置的。无论如何或何时调用该函数,this始终引用传递的值。好的。

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

在这种情况下,我们将回调的this绑定到MyConstructorthis的值。好的。

注意:jquery绑定上下文时,使用jQuery.proxydocs。这样做的原因是,在取消绑定事件回调时不需要存储对函数的引用。jquery在内部处理这个问题。好的。ECMAScript 6:使用箭头函数

ECMAScript 6引入了箭头函数,可以将其视为lambda函数。他们没有自己的this绑定。相反,在作用域中查找this就像查找一个正常变量一样。这意味着你不必打电话给.bind。这不是他们唯一的特殊行为,请参考MDN文档了解更多信息。好的。

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

设置回调的this—第2部分

一些接受回调的函数/方法也接受回调的this应该引用的值。这基本上与自己绑定它相同,但是函数/方法为您做了这件事。Array#map是这样一种方法。其签名为:好的。

1
array.map(callback[, thisArg])

第一个参数是回调,第二个参数是this应该引用的值。下面是一个人为的例子:好的。

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的$.ajax方法〔docs〕描述了一个名为context的选项:好的。

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

函数this.method被指定为click事件处理程序,但如果单击document.body,则记录的值将为undefined,因为在事件处理程序中,this指的是document.body,而不是Foo的实例。正如前面已经提到的,this所指的取决于如何调用函数,而不是如何定义函数。如果代码如下所示,则更明显的是函数没有对对象的隐式引用:好的。

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;

解决方法与上述相同:如果可用,使用.bindthis显式绑定到特定值好的。

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

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

1
2
3
4
var self = this;
document.body.onclick = function() {
    self.method();
};

或使用箭头功能:好的。

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

好啊。


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

  • 您可以使用bind()函数。
  • 将对context/this的引用存储在另一个变量中(参见下面的示例)。
  • 使用ES6箭头函数。
  • 更改代码/函数设计/体系结构,为此,您应该对JavaScript中的设计模式进行命令。
  • 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);

    如果您使用的是underscore.js-http://underlinejs.org/bind

    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,则应使用$.proxy方法,因为并非所有浏览器都支持bind

    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的上下文中的行为。

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

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

    这里我有一个名为person的构造函数函数。它有一个属性叫name,有四种方法叫sayNameVersion1sayNameVersion2sayNameVersion3sayNameVersion4。它们四个都有一个特定的任务。接受一个回调并调用它。回调有一个特定的任务,即记录Person构造函数函数实例的Name属性。

    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构造函数创建一个实例,并用niceCallback调用不同版本的sayNameVersionX方法(x表示1,2,3,4),看看有多少种方法可以操作this内部回调来引用person实例。

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

    绑定:

    bind的作用是创建一个新函数,并将this关键字设置为提供的值。

    sayNameVersion1sayNameVersion2使用bind操作回调函数的this

    1
    2
    3
    4
    5
    6
    this.sayNameVersion1 = function(callback){
        callback.bind(this)()
    }
    this.sayNameVersion2 = function(callback){
        callback()
    }

    第一个绑定this,方法本身内部有回调,第二个回调通过绑定到它的对象传递。

    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

    呼叫:

    call方法的first argument在附加call的函数中用作this

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

    1
    2
    3
    this.sayNameVersion3 = function(callback){
        callback.call(this)
    }

    它被称为如下:

    1
    p1.sayNameVersion3(niceCallback)

    适用:

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

    sayNameVersion4利用apply操纵this指人的客体。

    1
    2
    3
    this.sayNameVersion4 = function(callback){
        callback.apply(this)
    }

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

    1
    p1.sayNameVersion4(niceCallback)


    我们不能把它绑定到setTimeout(),因为它总是用全局对象(window)来执行,如果您想在回调函数中访问this上下文,那么通过使用bind()来实现回调函数,我们可以做到:

    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月)。


    另一种方法是handleEvent(evt)方法,这是dom2在事件侦听器中绑定this的标准方法,它允许您始终删除侦听器(以及其他好处):

    1
    2
    3
    4
    5
    6
    7
    8
    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


    这个问题围绕着this关键字在javascript中的行为展开。this的表现如下:

  • this的值通常由函数执行上下文决定。
  • 在全局范围内,this表示全局对象(window对象)。
  • 如果对任何函数启用了严格模式,那么EDOCX1的值(0)将为undefined,因为在严格模式下,全局对象是指undefined而不是window对象。
  • 站在点之前的对象就是这个关键字将绑定到的对象。
  • 我们可以使用call()bind()apply()明确设置此值。
  • 当使用new关键字(构造函数)时,它将绑定到正在创建的新对象。
  • 箭头函数不绑定this?-?相反,this在词汇上是绑定的(即基于原始上下文)
  • 正如大多数答案所表明的,我们可以使用arrow函数或bind()方法或self var。我将引用google javascript样式指南中关于lambdas(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或const self = this

    所以最好的解决办法是使用兰姆达斯,如下所示:

    1
    2
    3
    4
    5
    6
    function MyConstructor(data, transport) {
      this.data = data;
      transport.on('data', () => {
        alert(this.data);
      });
    }

    参考文献:

  • https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  • 箭头函数与绑定