协慌网

登录 贡献 社区

如何在回调中访问正确的 `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);
};

但它表现出同样的问题。

如何访问正确的对象?

答案

你应该知道什么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 [文档] ,它返回一个新的功能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();

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

  1. 您可以使用<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind" rel="noreferrer">bind</a> ()函数。
  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);
  });
}

这都是调用方法的 “神奇” 语法:

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