JavaScript递归setTimeout

14

我刚开始学习 JavaScript,所以希望这会是一些简单的东西。我想制作一个自动播放的图片幻灯片。这很简单,有一些教程可以参考,但出于某种原因我没有成功。这是我的代码:

var image1 = new Image();
var image2 = new Image(); 
var image3 = new Image();
image1.src = "images/website6.jpg";
image2.src = "images/website7.jpg";
image3.src = "images/sunset.jpg";
var images = new Array(
  "images/website6.jpg",
  "images/website7.jpg",
  "images/sunset.jpg"
);
setTimeout("delay(images,0)",2000);
function delay(arr,num){
  document.slide.src = arr[num % 3];
  var number = num + 1;
  setTimeout("delay(arr,number)",1000);
}

我试图更改的图像具有ID slide。而且我也有一些证据表明它有效。发生的情况是第一张图片加载。然后第二张图片加载(这意味着原始的setTimeout调用必须起作用)。然后什么都没有发生。对我来说,这表明递归不起作用。

我在其他语言中非常熟悉递归,因此我认为它必须只是语法或其他问题,但我似乎无法弄清楚。谢谢任何帮助。


取消第二个 setTimeout 中的参数引号。这将是我的第一个猜测。 - Cronco
有趣的是,如果我这样做,我就得不到第二张图片...这表明这就是使图像只改变一次的原因。此外,我看到的每个示例都有引号,这就是为什么我加了引号的原因。 - Paul
在引用变量的情况下存在问题,计时器正在运行完整的字符串 "delay(arr,number)" 而非将变量转换为其存储的值 - 这就是为什么取消引用它会起作用(尽管您仍然需要像这样引用它们... "delay('" + arr + "', '" + number + "')")。但无关紧要 - Pointy 的答案更好。我只是解释发生了什么。 - Rob Williams
1
这段代码不是递归的。setTimeout会在函数调用栈(递归函数所在的位置)清空时,通过事件循环异步地注册一个处理程序来调用它。 - ggorlen
3个回答

23
问题在于当你传入要被 setTimeout 调用的字符串时,这个字符串会在全局上下文中进行求值(等到触发时才会执行)。因此,为了避免这种情况并且出于其他原因考虑,最好传入实际函数。
setTimeout(function() { delay(images, 0); }, 2000);

function delay(arr, num) {
  document.slide.src = arr[num % 3];
  setTimeout(function() { delay(arr, num + 1); }, 1000);
}

在更为现代的浏览器中,您可以使用.bind()方法来绑定函数并创建一个预绑定到某个对象上的函数,用作this

setTimeout(delay.bind({arr: images, num: 0}), 2000);

function delay() {
  document.slide.src = this.arr[this.num % 3];
  setTimeout(delay.bind({arr: this.arr, num: this.num + 1}), 1000);
}

有得必有失,但这只是一个例子,表明有多种方法来完成同一个任务。


太棒了,现在它可以工作了。但我还是有点困惑为什么我的代码之前不能运行。你说它会在“稍后,在触发时”被评估。那具体是什么时候呢?为什么它从来没有到达过那个部分?另外,为什么使用匿名函数会改变这种情况?如果有一些很好的文档可以涵盖这个问题,我会感激提供一个链接。谢谢。 - Paul
问题在于字符串“delay(arr,number)”需要能够访问变量“arr”和“number”,以便它有意义。由于字符串“eval()”的工作方式(现在我想想,无论是在setTimeout调用时还是稍后),它是在不同的上下文中完成的,而不是您的超时函数本身; 这只是语言的工作方式。 - Pointy
当您使用一个真实的函数时,对该函数的解释发生在您调用“setTimeout()”的实际范围内。由于JavaScript的工作方式,该函数确实可以访问那里的局部变量。 - Pointy
我认为在这种情况下使用闭包的第一个版本更好。它避免了使用bind,因为bind只是在代码中引入了一个额外的项(this)。而且由于setTimeout是从delay()内部调用的,所以不需要第一个setTimeout调用,只需调用delay(),并可能从重复的setTimeout更改为单个setInterval。 - RobG
@RobG 是的,我同意,但我只是出于教学目的才包含它 :-) - Pointy

3

我对第二个setTimeout调用感到非常怀疑。为了更清晰地表达,我建议使用显式函数而不是字符串表达式。

setTimeout(function() { delay(arr, number); }, 1000);

0

更加简洁,同时将延迟和参数传递给setTimeout()

(function delay(arr, num) {
  document.slide.src = arr[num];
  setTimeout(delay, 1000, arr, (num + 1) % 3);
})(images, 0);

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