尝试在对象中使用setInterval时,我遇到了一些奇怪的行为。
这是我的代码:
var Person = {
speech: null,
tryToSpeak: function ()
{
this.speech = "hello";
self.setTimeout (this.speak, 1000);
},
speak: function ()
{
// prints out undefined
console.log (this.speech);
}
}
Person.tryToSpeak ();
当speak()
通过setTimeout()
运行时,它无法访问任何对象数据,例如speech
。这到底是怎么回事?这是不可避免的行为吗?
答案 0 :(得分:3)
这些方法不携带对象信息。您可以使用.bind
将方法绑定到对象:
window.setTimeout(this.speak.bind( this ), 1000);
答案 1 :(得分:2)
首先注意一些事项:
new
关键字创建“实例”。在我的回答中,我将使用person
代替Person
。window
对象的方法。 self.setTimeout
不正确。虽然在某些javascript实现中self
是窗口,但在其他实现中则不是,因此它是不可靠的。this
始终是指当前执行上下文,无论何时或如何发生。 setTimeout调用完全将该函数从其正常的对象上下文中取出 - 它只是该点的一个函数,因此在执行时它没有预期的“this”对象。我可以看到一些方法来解决这个问题。
使用bind“创建一个新功能,在调用时,将此关键字设置为提供的值。”:
window.setTimeout(this.speak.bind(this), 1000);
将动态传递给setTimeout的函数包含在动态设置this
的匿名函数中:
window.setTimeout(function(thisobj) {
return function() {
thisobj.speak.call(thisobj);
}
}(this), 1000);
我们正在使用一个函数来创建thisobj
参数的闭包,恰好是在调用setTimeout时使用this
作为当前此对象调用的。该函数本身返回一个函数,用于调用setTimeout,该函数使用call
(或者您可以使用apply
)为调用this
设置speak
对象。我们在这里真正完成的是复制bind
的功能而没有它的参数支持 - 所以除非你需要完整的跨浏览器和旧浏览器支持,否则请使用bind(在这种情况下你可以这样做,或者你可以加糖你的javascript所以绑定工作,我的答案底部的代码)。但是我想让你看看幕后发生了什么。
明确访问person对象的成员:
speak: function () {
console.log (person.speech); // instead of this.speech
}
将Person更改为对象构造函数,可用于创建新的“person”实例:
function Person() {
var me = this;
this.speech = null;
this.tryToSpeak = function () {
me.speech = "hello";
window.setTimeout(me.speak, 1000);
};
this.speak = function () {
console.log(me.speech);
};
}
var person = new Person();
person.tryToSpeak();
通过在构造函数中捕获this
的私有变量me
的副本,您可以稍后在其他方法中使用它。现在捕获的this
是有意义的,因为当您运行new Person()
时,会有一个执行上下文(与简单地按照您的方式声明对象时不同,执行上下文为window
)。这种方法的一个很大的缺点是对象的每个实例都有自己的函数副本,因为它们不能成为对象原型的一部分(为了能够访问私有变量me
); < / p>
我确信还有其他方法可以处理它。您没有解释为什么首先需要person
成为对象,所以我不知道实现目标的最佳模式。您可能不会需要多人对象,但您可能会再次。对背景和目的的更广泛理解将有助于我更好地指导你。
注意:要“填充”没有bind
的javascript实现,这样你就可以更容易地对函数使用bind了,你可以这样做(注意与本机实现有一些不同,请看同样的bind如上所述链接以获取详细信息):
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
答案 2 :(得分:1)
这是不可避免的。当您像这样传递函数时,它们会丢失执行上下文。
解决方案是捕获“this”变量:
var self = this;
setTimeout(function() { self.speak(); }, 1000);
答案 3 :(得分:0)
有点不同版本的ErikE's fourth suggestion执行相同的工作,但我认为代码更简单
function Person() {
this.speech = null;
}
Person.prototype.tryToSpeak = function () {
this.speech = "hello";
var person = this;
window.setTimeout(function() {person.speak();}, 1000);
};
Person.prototype.speak = function () {
console.log(this.speech);
};
var person = new Person();
person.tryToSpeak();
正如Erik所说,你甚至需要多个Person
对象并不是很清楚,但如果你这样做,这种技术可能是最简单的。
更新(基于ErikE的评论):此版本为Person
添加了一个名称,并将speech
作为参数传递,以明确指出谁是说什么。基本结构保持不变。
function Person(name) {
this.name = name;
}
Person.prototype.tryToSpeak = function (speech) {
var person = this;
window.setTimeout(function() {person.speak(speech);}, 1000);
};
Person.prototype.speak = function (speech) {
console.log(this.name + " says, '" + speech + "'.");
};
var lincoln = new Person("Abraham Lincoln");
var churchill = new Person("Winston Churchill");
lincoln.tryToSpeak("Four score and seven years ago...");
churchill.tryToSpeak("Never, never, never give up.");
重点是person
中的tryToSpeak
变量是存储在闭包中的局部变量,而不是对某些单例的引用。
答案 4 :(得分:-2)
我不知道你为什么会有弊端。这是典型的范围/参考错误。想想它一分钟,可能是什么 这个在说什么?答案:坚果。
所以你可以直接引用这个对象,如下所示:
// prints out Hello
console.log (Person.speach);
或者将它作为一个属性来传递,就像那样:
self.setTimeout (this.speak(this), 1000);
Jsfiddle两个案例: