JavaScript 回调函数丢失 'this'

17

我有一个发布器,在其中我失去了对象内的this。以下JavaScript代码片段的输出会先给我"some-id",然后是undefined。当我在回调函数中使用this时,作用域超出了对象范围,无法再使用this。如何让回调函数使用“this”或至少访问该对象?

由于我将创建多个对象,所以无法创建类似静态存储的东西。

以下是您可以用来重现我的问题的测试代码。我希望CheckBox.doSomething()返回this.id的值,这应该与此测试案例中的some-id相匹配。

function CheckBox(input_id) {
    this.id = input_id;
    this.doSomething();
    $('#some-element').click(this.doSomething);
}

Checkbox.prototype.doSomething = function() {
    alert(this.input_id);
}

var some_box = new CheckBox('some-id');
some_box.doSomething();
$('#some-element').click();

我甚至无法让它按照我的意愿工作:

function CheckBox2(input_id) {
    this.id = input_id;
    alert(this.id);
}

CheckBox2.prototype.doSomething = function() {
    alert(this.input_id);
}
var some_box = new CheckBox2('some-id');
some_box.doSomething();

可能是 JavaScript 中的 this 作用域 的重复问题。 - Lucero
@Lucero,该技术仅适用于动态创建的函数,而不适用于原型中的函数。 - Esailija
可能是重复的问题:jQuery/javascript事件 - 原型事件处理程序 - Felix Kling
3个回答

21

你的问题出在这一行代码:$('#some-element').click(this.doSomething);

为什么这是个问题

JavaScript 方法不知道应该分配给 this 的对象,它是在方法被显式调用(使用myFunction.call(obj))或隐式调用(使用obj.myFunction())时设置的。

例如:

var x = {
    logThis: function () {
        console.log(this);
    }
};

x.logThis(); // logs x
x.logThis.call(y); // logs y

var func = x.logThis;
func(); // logs window: the fallback for when no value is given for `this`

在您的情况下,您将this.doSomething传递给了jQuery,然后jQuery使用被点击元素作为this值来显式调用它。发生的情况是(稍微复杂一些版本的)这样:

var callback = this.doSomething;
callback.call(anElement, anEvent);

解决方案

您需要确保使用正确的this值调用doSomething函数。您可以通过将其放入另一个函数中来实现:

var cb = this;
$('#some-element').click(function() {
    return cb.doSomething();
});

jQuery提供了一个proxy函数,让你可以更简单地实现这个功能:

$('#some-element').click(jQuery.proxy(this.doSomething, this));

在这种情况下,当我调用some_box.doSomething();时,它已经给了我“undefined”。这怎么可能?我没有使用任何回调函数。 - Anyone
你的代码中有一个错别字:在构造函数中,你设置了 this.id,但在 doSomething 函数中,你引用了 this.input_id - georgebrock
我会更加强调使用$.proxy,它可以使你的类可扩展,并将所有函数整齐地放在原型中。 - Esailija
@Esailija:我认为理解代码为什么不起作用比仅仅了解特定的问题解决方式更有益。如果你要使用像this.func = $.proxy(this.func, this)这样的技巧,那么你应该真正了解它是如何以及为什么起作用的。虽然我相信你已经知道,但是其他读者可能不知道。 - georgebrock
@georgebrock,我并不是想和我的答案进行比较,只是想指出说函数绑定仅仅是一种更简单的方式是低估了它的许多其他好处。无论如何,你的答案显然更好。 - Esailija

11
function CheckBox(input_id) {
    this.id = input_id;
    this.doSomething = $.proxy( this.doSomething, this );
    $('#some-element').click(this.doSomething);
}

这个的"javascript等效方法"是Function#bind,但并非每个浏览器都支持,因此看起来你正在使用jQuery,我会使用jQuery等效方法$.proxy


是的,我可以看到...一天太多JavaScript了:3 非常感谢!!现在我明白了为什么我之前用不同的对象创建方法会遇到这么多未定义的变量问题。感谢您提供清晰的例子。 - Anyone

4

其他人已经解释了问题的原因以及如何使用jQuery修复它。现在需要讲述的是如何使用标准JavaScript来修复它。不要再使用...

$('#some-element').click(this.doSomething);

... you write:

document.getElementById('some-element').addEventListener('click', this.doSomething.bind(this));

这会改变 doSomething 函数内部的this上下文。您还可以使用匿名函数来实现此功能 - 而不是...

$('#some-element').click(function(event) {
    console.log(this);
});

你写的内容是:

document.getElementById('#some-element').addEventListener('click', (function(event) {
    console.log(this);
}).bind(this));

在有许多回调函数的项目中,例如在Node.js中(您不必担心过时的浏览器),这对我非常有用。

编辑:getElementById()addEventListener()代替$(...).click(...)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接