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

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

function MyConstructor(data, transport) {this.data = data;transport.on('data', function () {alert(this.data);});}
// Mock transport objectvar transport = {on: function(event, callback) {setTimeout(callback, 1000);}};
// called asvar 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);};

但它表现出同样的问题。

如何访问正确的对象?

583915 次浏览

你应该知道的this

this(又名“上下文”)是每个函数中的一个特殊关键字,它的值仅取决于如何调用函数,而不取决于它的定义方式/时间/位置。它不像其他变量那样受到词法范围的影响(箭头函数除外,见下文)。以下是一些示例:

function foo() {console.log(this);}
// normal function callfoo(); // `this` will refer to `window`
// as object methodvar obj = {bar: foo};obj.bar(); // `this` will refer to `obj`
// as constructor functionnew foo(); // `this` will refer to an object that inherits from `foo.prototype`

要了解更多关于this的信息,请查看MDN留档


如何找到正确的this

使用箭头函数

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

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

不要使用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的值,因为它的值是自动设置的,但事实并非如此。

每个函数都有方法#0[docs],它返回一个this绑定到一个值的新函数。该函数的行为与您调用.bind的函数完全相同,只是this是您设置的。无论如何或何时调用该函数,this都将始终引用传递的值。

function MyConstructor(data, transport) {this.data = data;var boundFunction = (function() { // parenthesis are not necessaryalert(this.data);             // but might improve readability}).bind(this); // <- here we are calling `.bind()`transport.on('data', boundFunction);}

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

备注:当jQuery的绑定上下文时,请改用#0[docs]。这样做的原因是在解绑事件回调时不需要存储对函数的引用。jQuery在内部处理。

设置回调的this-第2部分

一些接受回调的函数/方法也接受回调的this应该引用的值。这基本上与自己绑定它相同,但函数/方法为您执行。#1[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的#1方法[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所指的取决于函数称为的方式,而不是定义的方式。
如果代码如下所示,则可能更明显的是该函数没有对对象的隐式引用:

function method() {console.log(this.data);}

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

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

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

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

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

或者使用箭头函数:

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

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

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

与上下文的麻烦

术语“上下文”有时用于指代这个引用的对象。它的使用是不合适的,因为它在语义或技术上不适合ECMAScript的这个

"背景"表示添加意义的事物周围的环境,或者提供额外意义的前后信息。术语“上下文”在ECMAScript中用于指代执行上下文,即某些执行代码范围内的所有参数、范围和这个

如图ECMA-262第10.4.2节所示:

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

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

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

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

这里有几种方法可以访问子上下文中的父上下文-

  1. 您可以使用bind()函数。
  2. 将对text/this的引用存储在另一个变量中(参见下面的示例)。
  3. 使用ES6Arrow函数。
  4. 更改代码、功能设计和架构-为此,您应该在JavaScript中使用设计模式以上的命令。

1.使用bind()函数

function MyConstructor(data, transport) {this.data = data;transport.on('data', ( function () {alert(this.data);}).bind(this) );}// Mock transport objectvar transport = {on: function(event, callback) {setTimeout(callback, 1000);}};// called asvar obj = new MyConstructor('foo', transport);

如果您使用Underscore.js-http://underscorejs.org/#bind

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

2.在另一个变量中存储对text/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);});}

首先,您需要清楚地了解#0#1关键字在#0上下文中的行为。

this


JavaScript中有两种类型的作用域。它们是:

  1. 全球范围

  2. 函数范围

简而言之,全局范围是指窗口对象。在全局范围中声明的变量可以从任何地方访问。

另一方面,函数作用域驻留在函数内部。在函数内部声明的变量通常无法从外部世界访问。

全局作用域中的#0关键字指的是窗口对象。函数中的#0也指的是窗口对象。因此,#0将始终引用窗口,直到我们找到一种方法来操作#0以指示我们自己选择的上下文。

---------------------------------------------------------------------------------                                                                              --   Global Scope                                                               --   (globally "this" refers to window object)                                  --                                                                              --   function outer_function(callback){                                         --                                                                              --       // Outer function scope                                                --       // Inside the outer function, the "this" keyword                       --       //  refers to window object                                            --       callback() // "this" inside callback also refers to the  window object --   }                                                                          --                                                                              --   function callback_function(){                                              --                                                                              --       // Function to be passed as callback                                   --                                                                              --       // Here "THIS" refers to the window object also                        --   }                                                                          --                                                                              --   outer_function(callback_function)                                          --   // Invoke with callback                                                    --                                                                              ---------------------------------------------------------------------------------

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

这里我有一个名为Person的构造函数。它有一个名为#0的属性和四个名为#1#2#3#4的方法。它们都有一个特定的任务。接受回调并调用它。回调有一个特定的任务,即记录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)}

现在,让我们创建一个实例从人构造函数和调用不同版本的#0(X指的是1,2,3,4)方法与#1看看有多少种方法我们可以操纵#2内部回调来引用#3实例。

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

绑定:

bind要做的是创建一个新函数,将#0关键字设置为提供的值。

sayNameVersion1sayNameVersion2使用bind操作回调函数的#2

this.sayNameVersion1 = function(callback){callback.bind(this)()}this.sayNameVersion2 = function(callback){callback()}

第一个方法在方法本身内部使用回调绑定#0。对于第二个,回调传递了绑定到它的对象。

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method
p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

调用

#1方法的#0在调用#1的函数中用作#2

sayNameVersion3使用#1操作#2来引用我们创建的人对象,而不是窗口对象。

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

它被称为如下:

p1.sayNameVersion3(niceCallback)

请求

#0类似,#1的第一个参数指的是将由#2关键字指示的对象。

sayNameVersion4使用#1来操纵#2来引用一个人对象

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

它的调用方式如下。简单地传递回调,

p1.sayNameVersion4(niceCallback)

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

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

另一种方法是自DOM2以来的标准方式在事件侦听器中绑定this,即让您始终删除监听器(以及其他好处),是EventListener接口中的handleEvent(evt)方法:

var obj = {handleEvent(e) {// always trueconsole.log(this === obj);}};
document.body.addEventListener('click', obj);

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

目前,如果在代码中使用类,还有另一种可能的方法。

类字段的支持下,可以通过以下方式实现:

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

当然,在引擎盖下,所有旧的好箭头函数都绑定了上下文,但在这种形式下,它看起来比显式绑定更清楚。

由于这是第3阶段提案,您将需要巴别塔和适当的Babel插件来处理它(08/2018)。

<强>应该了解“这个”关键字。

根据我的观点,你可以通过三种方式实现“this”(自|箭头函数|绑定方法)

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

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

在大多数情况下,this的值由函数的调用方式决定。

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

ES5引入了bind()方法来设置函数this的值,无论它是如何调用的。

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

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

参考

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

方法2:箭头函数-箭头函数表达式是正则函数表达式的语法紧凑替代方案,尽管没有自己绑定到this、参数、超级或new.target关键字。

箭头函数表达式不适合作为方法,它们不能用作构造函数。

参考箭头函数表达式

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

方法3:bind-bind()方法创建一个新函数,当调用该函数时,它的this关键字设置为提供的值,并在调用新函数时提供的任何参数之前提供给定的参数序列。

参考:Function.prototype.bind()

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

问题围绕this关键字在JavaScript中的行为展开。this的行为如下所示,

  1. this的值通常由函数执行上下文确定。
  2. 在全局作用域中,this指的是全局对象(window对象)。
  3. 如果为任何函数启用了严格模式,那么this的值将是undefined,因为在严格模式下,全局对象引用undefined代替window对象。
  4. 位于点之前的对象是this关键字将绑定到的对象。
  5. 我们可以使用call()bind()apply()显式设置this的值
  6. 当使用new关键字(构造函数)时,这将绑定到正在创建的新对象。
  7. 箭头函数不绑定this-相反,this是词法绑定的(即基于原始上下文)

正如大多数答案所建议的,我们可以使用箭头函数或#0方法或自 var。我想引用Google JavaScript风格指南中关于lambdas(箭头函数)的一点

更喜欢使用箭头函数而不是f.bind(this),尤其是在goog.bind(f, this)。避免写const self=this。箭头函数对于有时会意外传递的回调特别有用其他参数。

谷歌明确建议使用lambda而不是bind或const self = this

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

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

this在JavaScript中:

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

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

Node.js

module.exports.data = 'module data';// This outside a function in node refers to module.exports objectconsole.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();的。
  2. 即使obj2在点的左边,test2也通过bind()方法绑定到obj1this值是obj1
  3. obj2位于被称为:obj2.test3()的函数的点的左侧。因此obj2将是this的值。
  4. 在这种情况下:obj2.test4()obj2位于点的左侧。但是,箭头函数没有自己的this绑定。因此,它将绑定到外部作用域的this值,即module.exports开始记录的对象。
  5. 我们还可以使用call函数指定this的值。这里我们可以将所需的this值作为参数传入,在本例中为obj2

我遇到了Ngx折线图xAxisTickFormatting函数的问题,该函数是从这样的超文本标记语言调用的:[xAxisTickFormatting]="xFormat"

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

而不是像这样使用函数:

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

使用这个:

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

其他一些人已经谈到了如何使用. bind()方法,但具体来说,如果有人在让他们一起工作时遇到困难,您可以如何将其与. thi()一起使用:

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