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

时间:2011-03-07 22:55:58

标签: javascript

我有这个脚本:

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

3两次都会收到提醒,而不是1然后2

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

10 个答案:

答案 0 :(得分:311)

您必须安排为每个超时功能提供“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

答案 1 :(得分:153)

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

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

答案 2 :(得分:26)

这是因为

  1. 超时功能 循环完成后,回调都运行良好。事实上, 当计时器开始时,即使它在每次迭代时都是setTimeout(..,0),所有 这些函数回调仍将在完成后严格运行 循环,这就是3被反映的原因!
  2. 所有这两个函数,虽然已定义 在每个循环迭代中分别关闭相同的共享全局 范围,实际上只有一个i。
  3. 解决方案通过使用自我执行的函数(匿名的一个或更好的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(自执行函数)为每个迭代创建了一个新范围 迭代,它给我们的超时函数回调机会 关闭每个迭代的新范围,一个具有变量的范围 具有正确的每次迭代值,供我们访问。

答案 3 :(得分:25)

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

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

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

答案 4 :(得分:9)

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

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

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

答案 5 :(得分:7)

<强> ANSWER

我用它来制作动画,将物品添加到购物车中 - 点击时,购物车图标会从产品“添加”按钮浮动到购物车区域:

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

注意持续时间是单位时间n epocs。

因此,从点击时刻开始,动画开始epoc(每个动画片段)是每个一秒单位乘以项目数的乘积。

epoc https://en.wikipedia.org/wiki/Epoch_(reference_date)

希望这有帮助!

答案 6 :(得分:3)

您可以使用bind方法

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

答案 7 :(得分:2)

嗯,另一个基于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.

答案 8 :(得分:0)

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

假设我需要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

答案 9 :(得分:-14)

真正的解决方案就在这里,但您需要熟悉PHP编程语言。 你必须混合PHP和JAVASCRIPT订单才能实现你的目的。

注意这个:

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

它完全符合您的要求,但要注意如何在它们之间进行通气 PHP变量和JAVASCRIPT。