使用setTimeout调用自定义对象的方法会失去作用域

10
我在构建一个Javascript对象时遇到了问题,使用setTimeout调用该对象中的方法时也遇到了问题。我尝试了各种解决方法,但总是在循环的第二部分时作用域变成了window对象而不是我的自定义对象。警告:我对Javascript还比较新手。
我的代码:
$(function() {
 slide1 = Object.create(slideItem);
 slide1.setDivs($('#SpotSlideArea'));
 slide1.loc = 'getSpot';
 slide2 = Object.create(slideItem);
 slide2.setDivs($('#ProgSlideArea'));
 slide2.loc = 'getProg';
 slide2.slide = 1;
 setTimeout('triggerSlide(slide1)', slide1.wait);
 setTimeout('triggerSlide(slide2)', slide2.wait);
});

function triggerSlide(slideObject) {
 slideObject.changeSlide(slideObject);
}

var slideItem = {
 div1: null,
 div2: null,
 slide: 0,
 wait: 15000,
 time: 1500,
 loc: null,
 changeSlide: function(self) {
  this.slide ? curDiv = this.div1:curDiv = this.div2;
  $(curDiv).load(location.pathname + "/" + this.loc, this.slideGo(self));
 },
 setDivs: function(div) {
  var subDivs = $(div).children();
  this.div1 = subDivs[0];
  this.div2 = subDivs[1];
 },
 slideGo: function(self) {
  if(this.slide) {
   $(this.div2).animate({
    marginLeft: "-300px"
   }, this.time);
   $(this.div1).animate({
    marginLeft: "0"
   }, this.time);
   setTimeout('triggerSlide(self)', this.wait);
  } else {    
   $(this.div1).animate({
    marginLeft: "300px"
   }, this.time);
   $(this.div2).animate({
    marginLeft: "0"
   }, this.time);
   setTimeout('triggerSlide(self)', this.wait);
  }   
  this.slide ? this.slide=0:this.slide=1;
 }
}

我的最新尝试是构建helper函数triggerSlide,以便我可以尝试通过我的方法传递对象的引用,但即使如此也似乎不起作用。

我可以使用setInterval并且它可以工作,但是:

  1. 我想要确保动画完成后才重新启动计时器
  2. 我不知道如何解决这个问题。 :)
3个回答

16

对于刚开始学习Javascript语言(以及Stackoverflow)的程序员来说,这应该是必读的内容。

与Java不同,无论如何声明函数,都没有内在的将函数绑定到任何对象的“绑定”。只有在函数调用时才会发生对象上下文的绑定。因此,当你将对函数的引用传递给“setTimeout”之类的东西时,它并不重要你从某个对象获取了函数。它只是一个函数,“setTimeout”将使用默认上下文即window对象来调用它。

有许多处理这种情况的方法(我不会把它称为“问题”;这是该语言的一个事实,并且是一个非常强大的事实)。如果你正在使用某种框架,那么这将更容易。

另外一件事:你正在将超时和间隔处理程序绑定为包含代码的字符串。这是一种相当丑陋的做法,所以你应该习惯形成函数表达式—即其值为函数的表达式。

例如,对于你的“slideItem”处理程序,你可以这样做:

// ...
setTimeout(function() { slideItem.changeSlide(); }, 5000);
那样,由超时机制调用的函数将始终使用您的 "slideItem" 对象作为上下文来调用 "changeSlide"。 您不需要在 "changeSlide" 中使用 "self" 参数,因为 "this" 将指向正确的对象。 [编辑]我注意到实际上您正在使用一些jQuery,这是一个好事情。

在字符串中使用+1代码通常最好避免,除非绝对必要。这包括new Function('some code');setTimeout("doSomething()", 0);和每个人都喜欢的eval('some code');。前两者我从未见过必要性,第三个仅用于JSON和输入数学表达式 :-) - Andy E
我尝试了上述方法,初始调用可以正常工作,但是对于重复执行操作的调用(在slideGo()中的setTimeout()调用)仍然无法正常工作。我将它们格式化为与上面相同的方式,但是使用this.wait代替slideItem。即:setTimeout(function() { this.changeSlide(); }, this.wait);在Firebug中出现错误,指出this.changeSlide()不是一个函数。我知道在slideGo方法的早期,“this”引用我的slideItem,但是当第二个setTimeout调用发生时,它似乎并非如此。这就是我困惑的地方。 - user279898
我还应该提到,我尝试在slideGo方法的开头添加that=this,但似乎它被添加到全局范围而不仅仅是在方法内部。页面上有多个对象,因此只有一个对象会动画,由每个setTimeout触发。 - user279898
如果它不起作用,那就意味着你在调用“setTimeout”时没有正确设置。所有这些调用都应该使用我上面描述的匿名函数设置(假设“slideItem”是你实际想要使用的上下文对象;对于我来说有点难以理解正在发生什么)。 - Pointy
1
AAAARRRGH!! 在我疯狂追求力量和荣耀的过程中,显然我错过了一些至关重要的东西(现在我知道了)。JS 显然处理 "that = this" 和 "var that = this" 有所不同。第二个完美地工作了。因此,我的 slideGo 代码现在是 setTimeout(function() { that.changeSlide(); }, this.wait);这意味着我可能可以减少方法的数量,并将 slideGo 中的代码移回 changeSlide,以简化事情(我最初就是这样做的,但一直在尝试解决这个问题)。感谢 Pointy 的出色解释。 - user279898
太好了!很高兴听到你正在取得进展!是的,如果你在函数中给一个变量赋值,而没有加上"var",那么你实际上创建了一个全局变量!!这是一个非常容易犯的错误,直到你习惯一直打"var"。 - Pointy

0

我现在无法测试这段代码,但这有帮助吗?

setTimeout(function(){triggerSlide(slide1)}, slide1.wait);

呃,你为什么要将它作为字符串传递? - Pointy
与指定函数名称相反? - lance
是的,可以指定函数名称或匿名函数。在您的示例中,完全可以将引号去掉。然而,他的机制本来就过于复杂了,真正应该采用不同的方法来解决。 - Pointy
啊,没错。匿名函数是我的意图。字符串是我语法上的无知。我会更正。希望你的答案对他更有帮助。 - lance
很好。我们会看到的;JavaScript 是一个奇怪的语言,初学者很容易入门,但同时也很难。 - Pointy

0

这是 jQuery,对吗?animate() 有一个回调参数;把你想在动画完成后调用的函数传递给它,jQuery 将负责调用它。回调应该可以很好地捕获您想要的范围。


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