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


1425

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

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

但是也有同样的问题

如何访问正确的对象?


95
我不时对某个问题感到厌烦,因此决定写一个规范的答案。即使这些问题已被回答了百万次,但并非总是能够找到一个不会被无关信息“污染”的好问题+答案对。这是那些时刻之一,也是那些问题之一(我很无聊)。如果您认为针对此类问题实际上已经存在一个很好的规范问题/答案,请告诉我,我将删除该问题。欢迎提出改进建议!
Felix Kling 2013年



Answers:


1789

你应该知道什么 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文档


怎样指称正确 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,它的值MyConstructorthis

注意:当为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));
}

this回调-第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的$.ajax方法[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;

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

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

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

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

或使用箭头功能:

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

39
菲利克斯(Felix),我之前已经读过这个答案,但从未回答。我越来越担心人们使用selfthat参考this。我有这种感觉,因为this在不同的上下文中使用了一个重载变量。而self通常对应于本地实例,并且that通常引用另一个对象。我知道您没有设置此规则,因为我已经看到它在许多其他地方都出现过,但这也是为什么我开始使用_this,但不知道其他人的感受如何(除了非均匀练习),这也是为什么结果。
vol7ron 2014年

3
@FelixKling可以安全地假设在原型函数内部使用此函数始终具有预期的行为,而不管它们(通常)如何被调用?在原型函数中使用回调时,是否可以使用bind(),self或其他替代方法?
andig 2015年

5
@FelixKling有时依赖Function.prototype.call ()和可能很有用Function.prototype.apply ()。特别是与apply ()我取得了很多里程。bind ()尽管我知道(但不确定)使用bind相对于其他选项可能会有一点开销,但我不太倾向于仅出于习惯而使用。
Nolo

5
很好的答案,但考虑添加一个附加的可选解决方案,就是根本不使用类,新类或根本不使用它。
Aluan Haddad '02

4
re箭头功能“相反,它像普通变量一样在范围内查找。” 完全为我点击了此按钮,谢谢!() => this.clicked();)
alphanumeric0101

211

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

  1. 您可以使用 bind()功能。
  2. 将对上下文/ this的引用存储在另一个变量中(请参见下面的示例)。
  3. 使用ES6 Arrow功能。
  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);

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

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

2将对上下文/ 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);
  });
}

1
bind()选项令人惊讶,它只是传递了此对象的指针作为另一个对象上的this(:谢谢!
Stav Bodik

bind()就像一个魅力。非常感谢我+1 :)
Anjana Silva

56

所有这些都是调用方法的“魔术”语法:

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

33

“上下文”的麻烦

术语“上下文”有时用于表示this引用的对象。它的使用是不合适的,因为它在语义上或技术上都不适合ECMAScript的this

“上下文”是指围绕某些事物增加含义的环境,或一些提供额外含义的前后信息。术语“背景”中ECMAScript是用来指执行上下文,这是所有的参数,范围和的一些执行的代码的范围内。

这在ECMA-262第10.4.2节中显示

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

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

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

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


不能同意这个答案。术语“执行上下文”的存在并没有使“上下文”的其他使用违法,也没有使“执行”的其他使用违法。也许有一个更好的术语来描述,this但是这里没有提供,可以说“上下文”关门已经为时已晚。
Roamer-1888

@ Roamer-1888-谢谢您的编辑。您是对的,但我的论点不依赖于“执行上下文”的存在,而出于某些其他目的排除了“上下文”的存在。相反,它基于“上下文”,从技术和语义角度来看都是不合适的。我也认为使用“上下文”而不是“ this”正在消失。我看不出有任何理由找到thisthisBinding的替代术语,它只是混淆和意味着您必须在某个时候解释“上下文”实际上是this,而无论如何都不是“上下文”。:-)
RobG

当您已经承认它是执行上下文的一部分时,我认为您不能说绝不是“上下文”,其中“执行”只是形容词。
Roamer-1888

@ Roamer-1888-在这一点上,我不会继续进行此对话。是的,是执行上下文的一部分。说的上下文,就像说一个团队的成员就是团队。
RobG

RobG,可惜您不想继续。这是一个有趣的辩论。谢谢您给我的时间。
Roamer-1888

31

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

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

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

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

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

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

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

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

方法1:自我-自我用于维护对原始内容的引用,即使上下文在变化。这是事件处理程序中经常使用的一种技术(尤其是在闭包中)。

参考https : //developer.mozilla.org/zh-CN/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/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions

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

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

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

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

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

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

25

首先,您需要在的上下文中scopethis关键字有清晰的了解和行为scope

thisscope


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

   1) Global Scope

   2) Function Scope

简而言之,全局作用域是指window对象。在全局作用域中声明的变量可以从任何地方访问;另一方面,函数作用域位于函数内部。在函数内部声明的变量通常不能从外部访问。this全局范围内的关键字是指window对象。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)

}

现在,让我们从person构造函数创建一个实例,并调用sayNameVersionX(X表示1,2,3,4)方法的不同版本,niceCallback以了解我们可以使用多少种方法来操纵this内部回调来引用该person实例。

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

绑定:

绑定的作用是创建一个新函数,并将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

致电:

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

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

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

它的名称如下:

p1.sayNameVersion3(niceCallback)

适用:

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

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

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

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

p1.sayNameVersion4(niceCallback)

1
任何有关答案的建设性批评将不胜感激!
AL-zami

1
全局范围中的this关键字不一定引用window对象。只有在浏览器中才是这样。
兰德尔·弗拉格

1
@RandallFlagg我从浏览器的角度写了这个答案。如有需要,请随意增强此答案:)
AL-zami

19

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

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

9
这与现有答案有何不同?
菲利克斯·克林

13

这个问题围绕着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);
  });
}

参考文献:

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

这个问题专门关于使用函数/方法作为回调。您的答案可能更适合stackoverflow.com/q/3127429/218196
菲利克斯·克林

@FelixKling是的问题是关于使用函数/方法作为回调的那个主要问题是由于要处理this关键字多数民众赞成,所以我将答案分为两部分,一是this关于将函数/方法用作回调的第二部分。随时编辑答案。
Code_Mode

我发现你的第四点措辞含糊。考虑示例“使用带有此对象作为回调的方法时的问题”,其中正确的对象位于点之前,但上下文仍然不是该对象。
bleistift2

7

当前,如果在代码中使用类,则还有另一种方法。

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

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

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

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


2
这正是我在Typescript中工作的方式:public methodName = (params) => { body }在一个类中。
yeyeyerman

5

另一种方法,这是因为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


0

this 在JS中:

thisJS中的值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);

输出:

在此处输入图片说明

让我一一介绍输出(忽略从第二个开始的第一个日志):

  1. thisobj2因为点规则的左侧,我们可以看到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在这种情况下就是这样。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.