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

时间:2013-11-29 06:13:11

标签: 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);

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

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

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

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

但它也表现出同样的问题。

如何访问正确的对象?

13 个答案:

答案 0 :(得分:1538)

您应该了解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

不要使用this

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

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

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

在这种情况下,我们将回调的this绑定到MyConstructor的{​​{1}}的值。

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

ECMAScript 6:使用arrow functions

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

.bind

设置回调的function MyConstructor(data, transport) { this.data = data; transport.on('data', () => alert(this.data)); } - 第2部分

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

this

第一个参数是回调,第二个参数是值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 的值通常会在该函数/方法的文档中提及。例如,jQuery's $.ajax method [docs]描述了一个名为this的选项:

  

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


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

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

考虑以下示例:

context

函数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();
};

答案 1 :(得分:176)

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

  1. 您可以使用bind()功能。
  2. 将对context / this的引用存储在另一个变量中(参见下面的示例)。
  3. 使用ES6 Arrow函数。
  4. 更改代码/功能设计/架构 - 为此,您应该在javascript中使用design patterns命令。
  5. 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);
      });
    }
    

答案 2 :(得分:43)

这一切都在魔法&#34;调用方法的语法:

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

答案 3 :(得分:22)

&#34; context&#34;

的问题

术语&#34; context&#34;有时用于指代 this 引用的对象。它的使用是不合适的,因为它在语义上或技术上都不适合ECMAScript's this

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

这显示在ECMA-262 section 10.4.2

  

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

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

执行上下文提供了周围信息,这些信息为正在执行的代码增加了意义。它包含的信息仅包含thisBinding

因此 this 的价值不是&#34;#34;上下文&#34;,它只是执行上下文的一部分。它本质上是一个局部变量,可以通过调用任何对象并在严格模式下设置为任何值。

答案 4 :(得分:18)

首先,您需要清楚地了解 scope 以及 this 关键字在 {{ 1}}

scope&amp; this

scope
简而言之,全局范围是指窗口对象。在全局范围内声明的变量可以从任何地方访问。另一方面,函数范围驻留在函数内部。函数内部声明的变量通常无法从外部访问。全局范围内的 there are two types of scope in javascript. They are : 1) Global Scope 2) Function Scope 关键字是指窗口对象。 this 里面的函数也指窗口对象。所以 {{1在我们找到操作 this 的方法来指示我们自己选择的上下文之前,<}> 将始终引用该窗口。

this

在回调函数中操纵this的不同方法:

这里我有一个名为Person的构造函数。它有一个名为 -------------------------------------------------------------------------------- - - - 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 的方法, name sayNameVersion1 ,<强> sayNameVersion2 即可。它们中的所有四个都有一个特定的任务。接受一个回调并调用它。回调有一个特定的任务,就是记录Person构造函数实例的name属性。

sayNameVersion3

现在让我们从人员构造函数创建一个实例,并使用 {调用 sayNameVersion4 (X指的是1,2,3,4)方法的不同版本{1}} 了解我们可以通过多少种方式操纵 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 实例。< / p>

niceCallback

bind :

使用 this 关键字设置为提供的值,创建新功能的做法是什么。

personvar p1 = new Person('zami') // create an instance of Person constructor 使用bind来操纵回调函数的 this

sayNameVersion1

第一个绑定 sayNameVersion2 并在方法本身内部进行回调。对于第二个,回调是在绑定了对象的情况下传递的。

this

call :

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

apply :

this 类似, this.sayNameVersion3 = function(callback){ callback.call(this) } 的第一个参数指的是由 p1.sayNameVersion3(niceCallback) 指示的对象关键字。

call使用 apply 来操纵 this 来引用人物对象

sayNameVersion4

并且它被调用如下。简单地传递回调,

apply

答案 5 :(得分:15)

我们无法将此绑定到setTimeout(),因为它始终使用全局对象(Window)执行,如果要在回调函数中访问this上下文然后通过使用bind()来回调函数我们可以实现:

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

答案 6 :(得分:14)

您应该了解“此”关键字。

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

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

严格模式和非严格模式也有一些区别。

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

在执行过程中不能通过赋值来设置它,每次调用该函数时可能会有所不同。

ES5引入了bind()方法来设置函数this的值,而不管其调用方式如何,

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

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

Method2 :箭头函数-箭头函数表达式在语法上紧凑于常规函数表达式

尽管没有与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);
    });
}

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

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

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

参考: 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);

答案 7 :(得分: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才能立即进行处理(2018年8月8日)。

答案 8 :(得分:2)

另一种方法是自DOM2起在事件监听器中绑定this的标准方式,让您始终删除监听器(还有其他好处) ),就是handleEvent(evt)界面中的EventListener方法:

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

答案 9 :(得分:2)

this在JS中:

JS中this的值由函数的调用方式(而不是如何定义)确定100%。通过“点规则的左侧”

,我们可以相对容易地找到this的值:
  1. 使用function关键字创建函数时,this的值是该函数点(称为点)左侧的对象
  2. 如果点中没有剩余的对象,则函数内的this的值通常是全局对象(节点中的global,浏览器中的window)。我不建议在这里使用this关键字,因为它不如使用window这样的关键字!
  3. 存在某些构造,例如箭头函数和使用Function.prototype.bind()创建的函数,该函数可以固定this的值。这些是该规则的例外,但是对于固定this的值确实很有帮助。

nodeJS中的示例

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

输出:

enter image description here

让我一路接一路地浏览输出(忽略从第二个开始的第一个日志):

  1. this之所以为obj2,是因为点规则的左侧,我们可以看到test1被称为obj2.test1();的方式。 obj2位于点的左侧,因此this的值。
  2. 即使obj2位于圆点的左侧,test2仍通过obj1方法绑定到bind()。因此this的值为obj1
  3. obj2在函数obj2.test3()的点左边。因此,obj2将是this的值。
  4. 在这种情况下:obj2.test4() obj2位于圆点的左边。但是,箭头函数没有自己的this绑定。因此,它将绑定到外部范围的this值,该值是module.exports最初记录的对象。
  5. 我们还可以使用this函数来指定call的值。在这里,我们可以传递所需的this值作为参数,在这种情况下为obj2

答案 10 :(得分:1)

问题围绕着this关键字在javascript中的表现。 this的行为如下,

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

正如大多数答案所暗示的,我们可以使用箭头函数或bind()方法或Self 变量。我会引用Google JavaScript Style Guide

中有关lambdas(箭头函数)的观点
  

在f.bind(this)上尤其是在箭头上优先使用箭头功能   goog.bind(f,this)。避免编写const self = this。箭头功能   对于回调有时会意外传递,特别有用   其他参数。

Google显然建议使用Lambda而不是绑定或const self = this

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

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

答案 11 :(得分:1)

我遇到了Ngx折线图xAxisTickFormatting函数的问题,该函数是从HTML调用的,例如:[xAxisTickFormatting]="xFormat"。我无法从声明的函数访问组件的变量。此解决方案帮助我解决了问题,以找到正确的解决方案。希望这对Ngx折线图的用户有所帮助。

而不是像这样使用函数:

xFormat (value): string {
  return value.toString() + this.oneComponentVariable; //gives wrong result 
}

使用此:

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

答案 12 :(得分:1)

其他一些人已经谈到了如何使用 .bind() 方法,但特别是这里是如何将它与 .then() 一起使用,如果有人在让它们一起工作时遇到问题

someFunction()
.then(function(response) {
    //'this' wasn't accessible here before but now it is            
}.bind(this))

编辑:如评论中所述,另一种方法是使用没有自己的“this”值的箭头函数

someFunction()
.then((response)=>{
    //'this' was always accessible here         
})