【问题标题】:setTimeout in for-loop does not print consecutive values [duplicate]for循环中的setTimeout不打印连续值[重复]
【发布时间】:2011-07-10 17:21:01
【问题描述】:

我有这个脚本:

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

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

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

【问题讨论】:

  • 这里的答案都不起作用。他们每个人都只是延迟设定的时间,然后立即运行整个循环而不会再延迟。查看 OP 的代码,他们显然希望每次迭代都有延迟。
  • 另外值得注意的是,如果用户确实希望警报同时触发,那么设置多个 setTimeouts 并不是最好的方法。
  • 使用“let”关键字代替var,这样就解决了。
  • 我正在尝试类似的事情,但没有人能够从概念上回答这个问题,或者解释我做错了什么。这可能是您需要了解的内容: setTimeout() 是异步的:JS 引擎在继续之前不会等待 n 毫秒(在您的示例中为 100 毫秒)。它只是做了一个“心理记录”:“100 毫秒后,执行(在这种情况下)警报”,然后继续执行循环。它会在 100 毫秒结束之前完成所有 3 次(或 300 次)迭代,因此最终,当该时间过去时,它会一次发出所有 3 次(或 300 次)警报。
  • 我认为您可以使用let 而不是var。这将解决您的问题

标签: javascript


【解决方案1】:

您可以使用extra arguments to setTimeout 将参数传递给回调函数。

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

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

【讨论】:

【解决方案2】:

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

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

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

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

edit — 随着时间的推移,有几个 cmets 显然有些混淆,因为设置几个超时会导致所有处理程序同时触发。了解设置计时器的过程(对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

【讨论】:

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

这是因为

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

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

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(self-executed function) 为每个迭代创建了一个新范围 迭代,这给了我们的超时函数回调机会 为每次迭代关闭一个新范围,该范围有一个变量 其中包含正确的每次迭代值供我们访问。

【讨论】:

  • 当我将 i 变量设置为更大的数字(3 或更大)时,它发出的警报的数字顺序变得奇怪。你能解释一下为什么吗?这是因为 setTimeout 或警报?非常感谢。跨度>
  • 谢谢,因为你说的问题,为了演示,我把 alert() 改成了 console.log()。至少在 chrome 中它可以正常工作!关于这个问题,请查看这个问题Question
【解决方案4】:

一旦我解决了这个问题,我就遇到了同样的问题。

假设我想要 12 次延迟,间隔为 2 秒

    function animate(i){
         myVar=setTimeout(function(){
            alert(i);
            if(i==12){
              clearTimeout(myVar);
              return;
            }
           animate(i+1)
         },2000)
    }

    var i=1; //i is the start point 1 to 12 that is
    animate(i); //1,2,3,4..12 will be alerted with 2 sec delay

【讨论】:

  • animate(i); - i 未定义,整个方法只有在调用animinate(1) 时才能正常工作,其他任何值都不能正常工作。该参数充其量是毫无意义的。
  • 这应该会造成 12 个连续的延迟。这里的“i”是更改延迟的数量。如果为1,则为12。如果为11,则为两个。
【解决方案5】:

真正的解决方案在这里,但您需要熟悉 PHP 编程语言。 您必须混合使用 PHP 和 JAVASCRIPT 命令才能达到您的目的。

注意这一点:

<?php 
for($i=1;$i<=3;$i++){
echo "<script language='javascript' >
setTimeout(function(){alert('".$i."');},3000);  
</script>";
}
?> 

它正是你想要的,但要小心如何在 PHP 变量和 JAVASCRIPT 变量。

【讨论】:

  • 这通过为每个呼叫创建一个单独的计时器来工作,但它们都同时触发,所以??
  • 不再插值......它可以混合全新的两个宇宙。
【解决方案6】:

你可以使用bind方法

for (var i = 1, j = 1; i <= 3; i++, j++) {
    setTimeout(function() {
        alert(this);
    }.bind(i), j * 100);
}

【讨论】:

  • 这是设置数字为this。但是点了。与此类似,您也可以这样做setTimeout(console.log.bind(console,i), 1000);
  • setTimeout(console.log, 1000,i);这也一样
【解决方案7】:

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

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

【讨论】:

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

嗯,基于 Cody 的回答但更通用的另一个可行的解决方案可能是这样的:

function timedAlert(msg, timing){
    setTimeout(function(){
        alert(msg);    
    }, timing);
}

function yourFunction(time, counter){
    for (var i = 1; i <= counter; i++) {
        var msg = i, timing = i * time * 1000; //this is in seconds
        timedAlert (msg, timing);
    };
}

yourFunction(timeInSeconds, counter); // well here are the values of your choice.

【讨论】:

    【解决方案9】:

    回答

    我将它用于将商品添加到购物车的动画 - 单击时,购物车图标会从产品“添加”按钮浮动到购物车区域:

    function addCartItem(opts) {
        for (var i=0; i<opts.qty; i++) {
            setTimeout(function() {
                console.log('ADDED ONE!');
            }, 1000*i);
        }
    };
    

    注意持续时间以 单位时间 n 个 epocs 为单位。

    所以从点击那一刻开始,动画开始 epoc(每个动画的)是每个一秒单位乘以项目数的乘积。

    epochttps://en.wikipedia.org/wiki/Epoch_(reference_date)

    希望这会有所帮助!

    【讨论】:

    • 您也可以将 args 传递给回调函数,例如:setTimeout(function(arg){...}, 1000*i, 'myArg');
    【解决方案10】:

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

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

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

    【讨论】:

    • 这不起作用:jsfiddle.net/sq5n52xj
    • 要工作,它只需要将延迟乘以 i。像这样: setTimeout(function () { alert(x); }, i*100);
    • 您只需要将 var 替换为 let 关键字,它会打印从 1 到 2 的数字。但这里又要注意了,这将仅在 2 秒后打印 1 和 2。如果要打印 1 和 2 的时间间隔分别为 1 秒,那么在 setTimeout 回调中,将 1000 修改为 i * 1000
    猜你喜欢
    • 2016-08-30
    • 2016-09-11
    • 2015-12-29
    • 2018-02-04
    • 2010-12-19
    • 1970-01-01
    相关资源
    最近更新 更多