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

coding2live 2021-01-08 11:48:20 76

我创建了一个构造函数,并且给它绑定了一个事件处理函数:

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你应该了解如下的内容:

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`

要了解更多这方面的信息,请查看MDN文档。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

如何正确的引用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值 - part 1

虽然this的值是自动设置的,但也并非是不能控制的。

每个函数都有.bind方法 查看详细,它返回一个新函数,并将这个函数绑定到一个值上。
这个函数与你调用的.bind函数的行为完全相同,只不过它是由你设置的。
无论如何或何时调用该函数,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查看更多
这样做的原因是,在解除回调函数的绑定时,不需要存储对函数的引用。jQuery自己会在内部处理。

设置this回调函数 - part 2

一些函数和方法在接收回调函数的同时,也接收这个回调函数的this应该指向的值。
这跟自己实现绑定的效果基本相同,只不过这些函数和方法替你做了这部分工作。
Array#map 查看更多就是这样一个方法。

它的用法是这样的:

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

注意: 是否可以给这个函数传递一个值,通常在该函数/方法的文档中有所提及。
例如,jQuery的$.ajax方法文档描述了一个叫做context的选项:

这个对象将成为所有与ajax相关的回调的上下文。

常见问题: 使用对象方法作为回调/事件处理方法

该问题的另一种常见表现形式是将对象方法用作回调/事件处理程序。
== Functions是JavaScript中的头等公民==,“method”只是一个通俗的术语,表示函数是对象属性的值。
但是这个函数没有"链接"到它“包含”的对象。

思考下面的例子:

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来显式地将它绑定到一个特定的值。

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

或者是将对象(this)绑定到另一个变量上:

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

或者使用箭头函数:

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