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

IT技术 javascript callback this
2021-01-04 21:07:49

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

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

但它表现出同样的问题。

如何访问正确的对象?

6个回答

你应该知道的 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

使用箭头函数

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因为它的值是自动设置的,但实际上并非如此。

每个函数都有方法.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);
}

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

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

设置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被分配为单击事件处理程序,但如果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();
@FelixKling 有时依赖Function.prototype.call ()Function.prototype.apply (). 特别是apply ()我已经获得了很多里程。我不太倾向于使用bind ()可能只是出于习惯,尽管我知道(但不确定)使用 bind 可能比其他选项有轻微的开销优势。
2021-02-20 21:07:49
Felix,我以前读过这个答案,但从未回复过。我越来越担心人们使用selfthat引用this. 我有这种感觉,因为this是在不同上下文中使用的重载变量;self通常对应于本地实例,that通常指的是另一个对象。我知道你没有设置这个规则,因为我在其他一些地方看到它出现过,但这也是我开始使用的原因_this,但我不确定其他人的感受,除了不统一的做法结果就是这样。
2021-03-04 21:07:49
@FelixKling,它可以让你与像代码超级懒惰$(...).on('click', $.proxy(obj, 'function'))$(...).off('click', obj.function)
2021-03-07 21:07:49

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

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

如果您使用的是 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(:谢谢!
2021-02-14 21:07:49
...parent context inside child context ”是一个误导性短语,因为不是父/子关系的一部分。它通常指调用方法的对象,但可以是任何对象,也可以是严格模式下的任何值。Context ”指的是无法引用的执行上下文(是许多参数中的一个),因为 ECMA-262 禁止它。
2021-02-28 21:07:49

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

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

应该 了解“this”关键字。

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

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

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

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

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

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

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

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

参考这个

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

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

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

参考箭头函数表达式

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

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

参考: Function.prototype.bind()

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

“上下文”的麻烦

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

“上下文”是指围绕某些事物增加意义的环境,或一些提供额外意义的前后信息。术语“上下文”在 ECMAScript 中用于指代执行上下文,它是所有参数、范围,以及一些执行代码范围内的this

这在ECMA-262 第 10.4.2 节中显示

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

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

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

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

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