为什么javascript setTimeout()不能在循环中工作

why does javascript setTimeout() not work in a loop?

本文关键字:循环 工作 不能 javascript setTimeout 为什么      更新时间:2024-03-05

考虑以下代码:

<!DOCTYPE html>
<html>
<head>
<script>
function timedText()
{
  var x=document.getElementById('txt');
  var t = new Array();
  t[1] = setTimeout( function(){x.value="2 seconds"}, 2000 );
  t[2] = setTimeout( function(){x.value="4 seconds"}, 4000 );
  t[3] = setTimeout( function(){x.value="6 seconds"}, 6000 );
}
function timedTextArr()
{
  var x=document.getElementById('txt');
  var t = new Array();
  for( var i = 0 ; i < 3 ; i++ ) {
    t[i] = setTimeout( function(){x.value=i*2+" seconds"}, i*2000 );
  }
}
</script>
</head>
<body>
<form>
<input type="text" id="txt" />
<input type="button" value="Display timed text!" onclick="timedText()" />
<input type="button" value="Display timed text Arr!" onclick="timedTextArr()" />
</form>
<p>Click on the button above. The input field will tell you when two, four, and six seconds have passed.</p>
</body>
</html>

函数timedText()起作用,但timedTextArr()不起作用。这两个函数都将setTimeout()的返回值分配给数组元素。但在for()循环中,只有最后一个计时器工作。。。它工作了三次。

这是个虫子吗?

这不是一个bug,看看Javascript中的闭包是什么。

基本上在你的for循环功能

function(){x.value=i*2+" seconds"}

只"看到"i变量的一个实例。

循环结束后,i等于3,所以所有函数都是3。

您需要将调用封装在另一个匿名函数中以创建一个作用域,如下所示:

t[i] = setTimeout( (function(i){ return function(){x.value=i*2+" seconds"}})(i), i*2000 );

外部函数将创建一个新的作用域,在它内部,i将等于循环中i的值,并保持这样。你可以在那里试试:http://jsfiddle.net/6b68E/

函数中的i指的是循环中的i,在任何超时触发时为6。您需要添加一个闭包/作用域:

for (var i = 0 ;i < 3 ;i++ ) {
  // create a closure (new scope)
  (function() {
    // make a local copy of `i` from the outer scope
    var _i = i;
    // use `i` for values that should refer to the *latest* value.
    // use the copy (`_i`) for those that need the point-in-time value.
    t[i] = setTimeout(function() { x.value = _i * 2 + " seconds" }, i*2000);
  })();
}

更新2023

由于引入了let,您现在可以将计数器变量的范围扩大到循环中。

for (let i = 0; i < 3; i++) {
  setTimeout(() => x.value = `${i * 2} seconds`, i * 2000);
}

获得相同结果的原因是setTimeout是异步的。这意味着它在脚本的其余部分完成之前不会运行。然后,一旦它运行,i的值就被设置为等于3,所以所有的函数都运行,它们看到的只是i=3。

主要问题是,由于定时器函数的异步性质,循环在调用第一个超时之前完成,因此在第一个超时运行时,i设置为2,在其他两个超时中保持不变

为了克服这一点,您应该考虑重构代码以使用间隔,这允许您与闭包同步更改i的值:

var i=1;
var handle = setInterval( function() {
x.value = (i*2) + "seconds";
i++;
if (i>3) clearInterval(handle);
}, 2000 );

除此之外,循环从0运行到2,而不是从1运行到3,就像在timedText() 中一样