ChatGPT解决这个技术问题 Extra ChatGPT

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

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

函数 MyConstructor(数据,传输){ this.data = 数据; transport.on('data', function () { alert(this.data); }); } // 模拟传输对象 var transport = { on: function(event, callback) { setTimeout(callback, 1000); } }; // 调用 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);
};

但它表现出同样的问题。

如何访问正确的对象?

Useful TypeScript page about this,也主要适用于 JS。
@strattonn:自答问题是 UI 中的一个选项,鼓励:stackoverflow.blog/2011/07/01/…。对于一遍又一遍地出现的问题,我这样做了几次,以提供规范的答案。规范问答的问题在于,现有问题通常没有以足够笼统的方式来表达,而不会集中在核心问题上。

F
Felix Kling

你应该知道什么

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

使用箭头函数

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

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

不要使用这个

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

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

由于 self 是一个普通变量,它遵循词法范围规则并且可以在回调中访问。这还有一个好处是您可以访问回调本身的 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 在内部处理它。

设置回调 - 第 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's $.ajax method [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();

菲利克斯,我以前读过这个答案,但从未回复过。我越来越担心人们使用 selfthat 来指代 this。我有这种感觉是因为 this 是在不同上下文中使用的重载变量;而 self 通常对应于本地实例,而 that 通常指代另一个对象。我知道您没有设置此规则,正如我在其他许多地方看到的那样,但这也是我开始使用 _this 的原因,但不确定其他人的感受,除了非-由此产生的统一实践。
@FelixKling,它可以让你超级懒惰地使用 $(...).on('click', $.proxy(obj, 'function'))$(...).off('click', obj.function) 之类的代码。
@FelixKling 有时依赖 Function.prototype.call ()Function.prototype.apply () 会很有用。特别是使用 apply (),我获得了很多里程。我不太倾向于使用 bind () 可能只是出于习惯,尽管我知道(但不确定)使用 bind 可能比其他选项有轻微的开销优势。
请务必注意,bind() 将在解释过程中第一次遇到它的上下文的快照...也就是说,当 JavaScript 到达 bind() 函数 第一次,此时它将获取 this 的上下文!由于无法更改 bind() 的标准实现,因此排除故障可能会很棘手。一旦一个函数绑定到另一个对象,它将保持绑定到该对象,并且尝试重新绑定它将不起作用。
P
Peter Mortensen

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

您可以使用 bind() 函数。将 context/this 的引用存储在另一个变量中(参见下面的示例)。使用 ES6 箭头函数。更改代码、功能设计和架构——为此,您应该掌握 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. 在另一个变量中存储对 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);
  });
}

bind() 选项令人惊叹,它只是将这个对象的指针传递给另一个对象上的 this (:谢谢!
…子上下文中的父上下文”是一个误导性的短语,因为 this 不是父/子关系的一部分。它通常指的是调用方法的对象,但可以是 any 对象,或严格模式下的 any 值。 “Context”指的是一个执行上下文(其中 this 是许多参数之一),因为 ECMA-262 禁止它,所以它不能被引用。
P
Peter Mortensen

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

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

A
Ashish

您应该知道“this”关键字。

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

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

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

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

执行时不能通过赋值来设置,每次调用函数时可能都不一样。

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

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

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

参考this

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

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

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

参考Arrow function expressions

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

Z
Zoe stands with Ukraine

“上下文”的问题

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

"Context" 是指围绕某事物增加意义的情况,或一些赋予额外意义的前后信息。术语“上下文”在 ECMAScript 中用于指代 execution context,即某些执行代码范围内的所有参数、范围和 this

这显示在 ECMA-262 section 10.4.2 中:

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

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

执行上下文提供了为正在执行的代码增加意义的周围信息。它包含的信息远不止 thisBinding

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


不能同意这个答案。 “执行上下文”一词的存在并不禁止“上下文”的其他用途,就像它禁止“执行”的其他用途一样。也许有一个更好的术语来描述 this,但这里没有提供任何术语,而且现在关闭“上下文”的大门可能为时已晚。
@Roamer-1888——感谢您的编辑。你是对的,但我的论点并不依赖于“执行上下文”的存在,而排除了“上下文”用于其他目的。相反,它基于从技术和语义角度来看都不合适的“上下文”。我也认为使用“context”而不是“this”正在消失。我看不出有任何理由找到 this 或 thisBinding 的替代术语,它只是混淆了,意味着在某些时候你必须解释“上下文”实际上就是这个,而且它无论如何都不是“上下文”。 :-)
当您已经承认它是执行上下文的一部分时,我认为您不能说这绝不是“上下文”,其中“执行”只是形容词。
@Roamer-1888——我不会在这之后继续这个对话。是的,这是执行上下文的一部分。说它的上下文就像说一个团队的一个球员就是团队。
P
Peter Mortensen

首先,您需要清楚了解 scope 以及 this 关键字在 scope 上下文中的行为。

this & scope

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

全局作用域函数作用域

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

另一方面,函数作用域位于函数内部。在函数内部声明的变量不能从外部正常访问。

全局范围内的 this 关键字指的是窗口对象。函数内部的 this 也指窗口对象。所以 this 将始终引用窗口,直到我们找到一种方法来操纵 this 以指示我们自己选择的上下文。

--------------------------------------------------------------------------------
-                                                                              -
-   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 的构造函数。它有一个名为 name 的属性和四个名为 sayNameVersion1sayNameVersion2sayNameVersion3 的方法> 和 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)
}

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

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

bind:

bind 所做的是创建一个将 this 关键字设置为提供值的新函数。

sayNameVersion1sayNameVersion2 使用绑定来操作回调函数的 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

call:

call 方法的 first argument 在使用 call 调用的函数内用作 this 附在上面。

sayNameVersion3 使用 call 来操纵 this 以引用我们创建的 person 对象,而不是 window 对象。

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

它被称为如下:

p1.sayNameVersion3(niceCallback)

apply:

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

sayNameVersion4 使用 apply 操纵 this 来引用人员对象

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

它被称为如下。只需传递回调,

p1.sayNameVersion4(niceCallback)

任何关于答案的建设性批评将不胜感激!
全局范围内的 this 关键字不一定引用窗口对象。这仅在浏览器中是正确的。
@RandallFlagg 我从浏览器的角度写了这个答案。如有必要,请随意增强这个答案:)
P
Peter Mortensen

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

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

回复“Window”:不是“window”(小写)吗?
P
Peter Mortensen

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

this 的值通常由函数执行上下文确定。在全局范围内,this 指的是全局对象(window 对象)。如果为任何函数启用了严格模式,那么 this 的值将是未定义的,因为在严格模式下,全局对象引用未定义来代替窗口对象。位于点之前的对象是 this 关键字将绑定到的对象。我们可以使用 call()、bind() 和 apply() 显式设置 this 的值。当使用 new 关键字(构造函数)时, this 将绑定到正在创建的新对象。箭头函数不绑定 this - 相反,this 是按词法绑定的(即,基于原始上下文)

正如大多数答案所暗示的那样,我们可以使用 箭头函数或 bind() Method 或 Self var。我会引用 Google JavaScript Style Guide 中关于 lambdas(箭头函数)的一点

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

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

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

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

参考:

https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c arrow-functions-vs-bind


这个问题专门关于使用函数/方法作为回调。您的答案可能更适合 stackoverflow.com/q/3127429/218196
我发现你的第四点措辞模棱两可。考虑 example “Problem When Using Methods With The this Object as Callbacks”,其中正确的对象位于点之前,但上下文仍然不是该对象。
P
Peter Mortensen

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

class fields 的支持下,可以通过以下方式实现:

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

可以肯定的是,在底层,绑定上下文的都是旧的好的箭头函数,但在这种形式下,它看起来比显式绑定要清楚得多。

由于它是第 3 阶段提案,因此您需要 Babel 和适当的 Babel plugin 来处理它,就像现在 (08/2018) 一样。


这正是我在 Typescript 中使用它的方式:public methodName = (params) => { body } 在一个类中。
P
Peter Mortensen

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

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

而不是使用这样的函数:

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
 }

P
Peter Mortensen

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

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

document.body.addEventListener('click', obj);

可以在此处找到有关使用 handleEvent 的详细信息:DOM handleEvent: a cross-platform standard since year 2000


P
Peter Mortensen

其他一些人已经谈到了如何使用 .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
})

这是不正确的。 (1) 箭头函数没有自己的 this 值,而是使用来自 closes this 提供环境的值。 (2) 因此,.bind 对箭头函数没有影响。
好电话,我复制了错误的代码,更新以显示两种变体
P
Peter Mortensen

这在 JavaScript 中:

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

当使用 function 关键字创建函数时,this 的值是被调用函数的点左侧的对象如果点的左侧没有对象,那么函数内部 this 的值通常是全局对象(全局在 Node.js 和浏览器中的窗口中)。我不建议在这里使用 this 关键字,因为它比使用 window!存在某些构造,例如箭头函数和使用 Function.prototype.bind() 创建的函数,该函数可以修复 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);

输出:

https://i.stack.imgur.com/VvehZ.png

让我一个一个地引导你完成输出(忽略从第二个开始的第一个日志):

这是 obj2 因为点规则的左边,我们可以看到 test1 是如何被称为 obj2.test1(); 的。 obj2 在点的左侧,因此是 this 值。即使 obj2 在点的左侧, test2 通过 bind() 方法绑定到 obj1。这个值是obj1。 obj2 位于被调用函数的点的左侧:obj2.test3()。因此 obj2 将是 this 的值。在这种情况下: obj2.test4() obj2 在点的左边。但是,箭头函数没有自己的 this 绑定。因此,它将绑定到外部范围的 this 值,即模块。导出开始时记录的对象。我们也可以使用 call 函数来指定 this 的值。在这里,我们可以传入所需的 this 值作为参数,在本例中为 obj2。