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

Fel*_*ing 1309 javascript callback 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);
Run Code Online (Sandbox Code Playgroud)

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

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

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

MyConstructor.prototype.alert = function() {
    alert(this.name);
};
Run Code Online (Sandbox Code Playgroud)

但它表现出同样的问题.

如何访问正确的对象?

Fel*_*ing 1664

你应该知道什么 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`
Run Code Online (Sandbox Code Playgroud)

要了解更多信息this,请查看MDN文档.


如何参考正确的 this

不要用 this

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

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}
Run Code Online (Sandbox Code Playgroud)

由于它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);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们将回调绑定thisMyConstructor's 的值this.

注意:绑定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));
}
Run Code Online (Sandbox Code Playgroud)

设置this回调-第2部分

一些接受回调的函数/方法也接受回调this应该引用的值.这与自己绑定基本相同,但函数/方法为您完成.Array#map [docs]就是这样一种方法.它的签名是:

array.map(callback[, thisArg])
Run Code Online (Sandbox Code Playgroud)

第一个参数是回调,第二个参数是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
Run Code Online (Sandbox Code Playgroud)

注意:您是否可以传递值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);
};
Run Code Online (Sandbox Code Playgroud)

该函数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;
Run Code Online (Sandbox Code Playgroud)

解决方案与上面提到的相同:如果可用,请使用.bind显式绑定this到特定值

document.body.onclick = this.method.bind(this);
Run Code Online (Sandbox Code Playgroud)

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

var self = this;
document.body.onclick = function() {
    self.method();
};
Run Code Online (Sandbox Code Playgroud)

或使用箭头功能:

document.body.onclick = () => this.method();
Run Code Online (Sandbox Code Playgroud)

  • 菲利克斯,我之前读过这个答案,但从未回复过.我越来越担心人们使用`self`和`that`来指代`this`.我觉得这样,因为`this`是一个在不同环境中使用的重载变量; 而`self`通常对应于本地实例,而`that`通常是指另一个对象.我知道你没有设定这个规则,因为我看到它出现在其他许多地方,但这也是我开始使用`_this`的原因,但我不确定其他人的感受,除了非 - 导致的统一做法. (35认同)
  • @FelixKling有时依赖`Function.prototype.call()`和`Function.prototype.apply()`会很有用.特别是`apply()`我已经获得了很多里程.我不太倾向于使用`bind()`也许只是出于习惯,虽然我知道(但不确定)使用bind比其他选项可能有轻微的开销优势. (5认同)
  • 很好的答案,但考虑添加一个额外的可选解决方案,只是为了不使用类,新的或这一点. (5认同)
  • @FelixKling可以安全地假设使用这个内部原型函数将始终具有预期的行为,无论它们(通常)被调用的方式如何?在原型函数中使用回调时,是否有一个替代bind(),self或者? (3认同)
  • 重新箭头函数"相反,它在范围内被查找,就像一个普通变量." 完全为我点击了这个,谢谢!`()=> this.clicked()`;) (3认同)
  • 需要注意的是,`bind()` 将对在解释过程中第一次遇到的上下文进行**快照**...也就是说,当 JavaScript 到达 `bind()` 函数**时第一次**,此时它将获取“this”的上下文!由于“bind()”的标准实现无法更改,因此排查问题可能会变得很棘手。一旦函数绑定到另一个对象,它将保持绑定到该对象,并且尝试重新绑定它是行不通的。 (3认同)

Moh*_*ere 196

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

  1. 你可以使用bind()功能.
  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);
Run Code Online (Sandbox Code Playgroud)

如果您正在使用underscore.js- http://underscorejs.org/#bind

transport.on('data', _.bind(function () {
    alert(this.data);
}, this));
Run Code Online (Sandbox Code Playgroud)

2将对context/this的引用存储在另一个变量中

function MyConstructor(data, transport) {
  var self = this;
  this.data = data;
  transport.on('data', function() {
    alert(self.data);
  });
}
Run Code Online (Sandbox Code Playgroud)

3箭头功能

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}
Run Code Online (Sandbox Code Playgroud)


Guf*_*ffa 47

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

object.property();
Run Code Online (Sandbox Code Playgroud)

当您从对象获取属性并一次调用它时,该对象将是该方法的上下文.如果您调用相同的方法,但是在单独的步骤中,则上下文是全局范围(窗口):

var f = object.property;
f();
Run Code Online (Sandbox Code Playgroud)

当您获得方法的引用时,它不再附加到对象,它只是对普通函数的引用.当您获得用作回调的引用时,会发生同样的情况:

this.saveNextLevelData(this.setAll);
Run Code Online (Sandbox Code Playgroud)

这就是你将上下文绑定到函数的地方:

this.saveNextLevelData(this.setAll.bind(this));
Run Code Online (Sandbox Code Playgroud)

如果您使用的是jQuery,则应该使用该$.proxy方法,因为bind并非所有浏览器都支持:

this.saveNextLevelData($.proxy(this.setAll, this));
Run Code Online (Sandbox Code Playgroud)


Rob*_*obG 25

"背景"的麻烦

术语"上下文"有时用来指通过引用的对象.它的使用是不合适的,因为它不适合任何语义或技术上与ECMAScript中的这个.

"上下文"是指围绕某些事物增加意义的情况,或者是一些提供额外含义的前后信息.ECMAScript中使用术语"上下文"来指代执行上下文,它是一些执行代码范围内的所有参数,范围和这一点.

这在ECMA-262第10.4.2节中显示:

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

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

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

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


Ash*_*ish 22

你应该知道"这个"关键字.

根据我的观点,你可以用三种方式实现"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);
    });
}
Run Code Online (Sandbox Code Playgroud)

方法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);
    });
}
Run Code Online (Sandbox Code Playgroud)

方法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);
Run Code Online (Sandbox Code Playgroud)


AL-*_*ami 21

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

this&scope:


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

   1) Global Scope

   2) Function Scope
Run Code Online (Sandbox Code Playgroud)

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

操纵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)

}
Run Code Online (Sandbox Code Playgroud)

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

var p1 = new Person('zami') // create an instance of Person constructor
Run Code Online (Sandbox Code Playgroud)

绑定:

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

sayNameVersion1sayNameVersion2使用bind来操纵this回调函数.

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}
Run Code Online (Sandbox Code Playgroud)

第一个与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
Run Code Online (Sandbox Code Playgroud)

电话:

first argument所述的call方法被用作this该被调用,该函数内call连接到它.

sayNameVersion3使用 call操纵this参考,而不是窗口对象,我们创建的Person对象.

this.sayNameVersion3 = function(callback){
    callback.call(this)
}
Run Code Online (Sandbox Code Playgroud)

它被称为如下:

p1.sayNameVersion3(niceCallback)
Run Code Online (Sandbox Code Playgroud)

申请:

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

sayNameVersion4用于apply操纵this以引用人物对象

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}
Run Code Online (Sandbox Code Playgroud)

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

p1.sayNameVersion4(niceCallback)
Run Code Online (Sandbox Code Playgroud)


小智 15

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

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);
Run Code Online (Sandbox Code Playgroud)

  • 这与现有的任何答案有何不同? (9认同)

Cod*_*ode 6

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

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

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

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

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

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

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}
Run Code Online (Sandbox Code Playgroud)

参考文献:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. 箭头功能与绑定


And*_*ddu 5

另一种方法是接口中的方法,这是自 DOM2 以来在事件侦听器中绑定的标准方法,它允许您始终删除侦听器(以及其他好处):thishandleEvent(evt)EventListener

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

document.body.addEventListener('click', obj);
Run Code Online (Sandbox Code Playgroud)

有关使用的详细信息handleEvent可以在这里找到:DOM handleEvent:自 2000 年以来的跨平台标准


sky*_*yer 5

当前,如果在代码中使用类,则可能存在另一种方法。

类字段 的支持下,可以进行其他操作:

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

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

由于是Stage 3 Proposal,因此您现在需要Babel和适当的Babel插件来处理它(08/2018)。

  • 这正是我在 Typescript 中使用它的方式:``public methodName = (params) =&gt; { body }`` 在一个类中。 (2认同)

Wil*_*een 5

this在 JavaScript 中:

JavaScript 中的值this100% 由函数的调用方式决定,而不是由函数的定义方式决定。this我们可以相对容易地通过“点左边规则”找到 的值:

  1. 当使用 function 关键字创建函数时,其值this是被调用函数的点左侧的对象
  2. 如果点左边没有对象,那么this函数内部的值通常是全局对象(global在 Node.js 和window浏览器中)。我不建议this在这里使用关键字,因为它比使用类似window!的关键字不太明确。
  3. 存在某些结构,例如箭头函数和使用 a函数创建的Function.prototype.bind()函数,可以固定 的值this。这些是规则的例外,但它们确实有助于修复 的值this

Node.js 中的示例

module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);

const obj1 = {
    data: "obj1 data",
    met1: function () {
        console.log(this.data);
    },
    met2: () => {
        console.log(this.data);
    },
};

const obj2 = {
    data: "obj2 data",
    test1: function () {
        console.log(this.data);
    },
    test2: function () {
        console.log(this.data);
    }.bind(obj1),
    test3: obj1.met1,
    test4: obj1.met2,
};

obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);
Run Code Online (Sandbox Code Playgroud)

输出:

在此输入图像描述

让我逐一引导您查看输出(忽略从第二个日志开始的第一个日志):

  1. thisobj2因为点规则的左边,我们可以看到如何test1调用obj2.test1();obj2是点的左边,因此是this值。
  2. 即使obj2是点的左边,test2也必须obj1通过该bind()方法。值为。thisobj1
  3. obj2是被称为函数的点的左侧:obj2.test3()。因此obj2将是 的值this
  4. 在本例中:obj2.test4() obj2是点的左侧。但是,箭头函数没有自己的this绑定。因此,它将绑定到this外部作用域的值,该外部作用域是module.exports在开始时记录的对象。
  5. this我们还可以使用该函数指定 的值call。这里我们可以传入所需的this值作为参数,就是obj2本例中的情况。


Md.*_*yan 5

Ngx我遇到了折线图函数的问题,xAxisTickFormatting该函数是从 HTML 调用的,如下所示:[xAxisTickFormatting]="xFormat"

我无法从声明的函数访问组件的变量。该解决方案帮助我解决了问题以找到正确的解决方案。

而不是使用这样的函数:

xFormat (value): string {
  return value.toString() + this.oneComponentVariable; //gives wrong result
}
Run Code Online (Sandbox Code Playgroud)

用这个:

 xFormat = (value) => {
   // console.log(this);
   // now you have access to your component variables
   return value + this.oneComponentVariable
 }
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

364007 次

最近记录:

6 年,5 月 前