for循环中的setTimeout不打印连续值

IT技术 javascript
2021-01-22 22:29:21

我有这个脚本:

for (var i = 1; i <= 2; i++) {
    setTimeout(function() { alert(i) }, 100);
}

但是3两次都收到警报,而不是1then 2

有没有办法 pass i,而不将函数写成字符串?

6个回答

您必须为每个超时函数安排一个不同的“i”副本。

function doSetTimeout(i) {
  setTimeout(function() { alert(i); }, 100);
}

for (var i = 1; i <= 2; ++i)
  doSetTimeout(i);

如果您不这样做(并且同一想法还有其他变体),那么每个计时器处理程序函数将共享相同的变量“i”。当循环结束时,“i”的值是多少?是3!通过使用中间函数,可以制作变量值的副本由于超时处理程序是在该副本的上下文中创建的,因此它有自己的私有“i”可供使用。

编辑- 随着时间的推移,有一些评论表明,设置一些超时会导致处理程序同时触发这一事实,这显然引起了一些混淆。重要的是要了解设置计时器的过程——调用setTimeout()——几乎不需要任何时间。也就是说,告诉系统“请在 1000 毫秒后调用此函数”几乎会立即返回,因为在计时器队列中安装超时请求的过程非常快。

因此,如果发出了一系列超时请求,就像 OP 中的代码和我的答案中的情况一样,并且每个请求的时间延迟值都相同,那么一旦该时间量过去,所有计时器处理程序将被一个接一个地快速连续调用。

如果您需要每隔一段时间调用处理程序,您可以使用setInterval(),它的调用方式完全相同,setTimeout()但在请求数量的重复延迟后将触发多次,或者您可以建立超时并乘以时间迭代计数器的值。也就是说,要修改我的示例代码:

function doScaledTimeout(i) {
  setTimeout(function() {
    alert(i);
  }, i * 5000);
}

100毫秒超时,效果不会很明显,所以我把数字提高到了5000。) 的值i乘以基本延迟值,因此在循环中调用5次将导致延迟5秒、10 秒、15 秒、20 秒和 25 秒。

更新

在 2018 年,有一个更简单的选择。有了在比函数更窄的范围内声明变量的新能力,如果这样修改原始代码将可以工作:

for (let i = 1; i <= 2; i++) {
    setTimeout(function() { alert(i) }, 100);
}

let声明,不像var,本身就会造成那里是一个独特i的循环的每个迭代。

我个人更喜欢匿名函数,因为我不想设置一堆名称。懒得想他们了。
2021-03-13 22:29:21
@Chuck 我同意 OP 不太可能希望两个警报同时响起,但我不确定。问题的重点在于警报中的是意外的(3 而不是 1 和 2)。我已经扩展了我的答案来讨论这样一个事实,即如果延迟相同,同时设置多个超时将导致它们同时触发。
2021-03-26 22:29:21
这是首选方法,因为它不会在循环体内产生函数定义。其他的可以工作,但不是可取的(即使它们确实显示了 JS 的惊人的坏蛋;))。
2021-03-31 22:29:21
@Pointy:这对我不起作用,js 等待 100,然后立即执行整个 for 循环。如果我做错了什么,请纠正我。
2021-03-31 22:29:21
@JAAulde 我承认我个人会用匿名函数来做,但作为例子,这种方式更好。
2021-04-05 22:29:21

您可以使用立即调用的函数表达式 ( IIFE ) 在 周围创建闭包setTimeout

for (var i = 1; i <= 3; i++) {
    (function(index) {
        setTimeout(function() { alert(index); }, i * 1000);
    })(i);
}

答案是不正确的。闭包已经存在,因为您可以alert(i)从匿名回调中调用。问题是i来自全局for块的闭包引用所以正确的答案是:IIFE 每次迭代都会创建额外的范围来绑定i并将其传递给匿名回调。然后i来自本地迭代范围的闭包引用
2021-03-13 22:29:21
我最短的方法:使用let代替varfor 循环
2021-03-13 22:29:21
使用自调用函数的好处的真实示例。
2021-03-18 22:29:21
这不起作用:jsfiddle.net/Ljr9fq88
2021-03-21 22:29:21
IIFE 只不过是无名函数和立即执行的便捷快捷方式——这就是 Accepted answer 实际上所做的,在完整的步骤中,没有快捷方式——将函数调用包装在另一个函数中,因此内部函数获得外部函数论证的本地副本。! !!
2021-03-28 22:29:21

这是因为

  1. 超时循环完成后函数回调都运行良好。事实上,随着定时器的运行,即使每次迭代都是 setTimeout(.., 0) ,所有这些函数回调仍然会在循环完成后严格运行,这就是为什么 3 被反映!
  2. 尽管它们在每次循环迭代中分别定义,但所有这两个函数都在相同的共享全局范围内关闭,实际上,其中只有一个 i 。

解决方案的通过使用执行的自功能(匿名一个或更好声明对于每次迭代单个范围IIFE)与具有的副本在里面,这样的:

for (var i = 1; i <= 2; i++) {

     (function(){

         var j = i;
         setTimeout(function() { console.log(j) }, 100);

     })();

}

更干净的将是

for (var i = 1; i <= 2; i++) {

     (function(i){ 

         setTimeout(function() { console.log(i) }, 100);

     })(i);

}

在每次迭代中使用IIFE(自执行函数)为每次迭代创建了一个新的作用域,这让我们的超时函数回调有机会为每次迭代关闭一个新的作用域,其中一个变量具有正确的执行其中的迭代值供我们访问。

谢谢你,因为这个问题的,你说我改变了警报()执行console.log()用于演示的缘故。至少在 chrome 中它可以正常工作!关于这个问题,请检查这个问题
2021-03-11 22:29:21
当我将 i 变量设置为更大的数字(3 或更大)时,警报的编号顺序变得奇怪。你能解释一下为什么吗?这是因为 setTimeout 或警报?非常感谢。
2021-04-08 22:29:21

的函数参数setTimeout正在关闭循环变量。循环在第一次超时之前完成并显示 的当前值i,即3

由于 JavaScript 变量只有函数作用域,因此解决方案是将循环变量传递给设置超时的函数。您可以像这样声明和调用这样的函数:

for (var i = 1; i <= 2; i++) {
    (function (x) {
        setTimeout(function () { alert(x); }, 100);
    })(i);
}
这不起作用:jsfiddle.net/sq5n52xj
2021-03-11 22:29:21
你只需要用 let 关键字替换 var ,它就会打印从 1 到 2 的数字。但这里又是一个问题,这只会在 2 秒后打印 1 和 2。如果你想以1秒的间隔打印1和2,那么在setTimeout回调中,将1000修改为i * 1000
2021-03-15 22:29:21
要工作,它只需要将延迟乘以 i。像这样: setTimeout(function () { alert(x); }, i*100);
2021-03-28 22:29:21

您可以使用setTimeout额外参数将参数传递给回调函数。

for (var i = 1; i <= 2; i++) {
        setTimeout(function(j) { alert(j) }, 100, i);
}

注意:这不适用于 IE9 及以下浏览器。

此处有针对该 IE 问题的 polyfill;developer.mozilla.org/en-US/docs/Web/API/WindowTimers/...
2021-03-19 22:29:21
很好:) 没想到 setTimeout 接受第三个参数
2021-03-31 22:29:21