关于javascript:for循环中的setTimeout不会打印连续值

setTimeout in for-loop does not print consecutive values

本问题已经有最佳答案,请猛点这里访问。

我有这个剧本:

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

3两次都被警告,而不是1,然后是2

有没有一种方法可以在不将函数写入字符串的情况下传递i


您必须为每个超时函数安排一个单独的"i"副本。

1
2
3
4
5
6
function doSetTimeout(i) {
  setTimeout(function() { alert(i); }, 100);
}

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

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

edit&mdash;随着时间的推移,出现了一些注释,其中明显存在一些混淆,即设置一些超时会导致处理程序同时启动。重要的是要了解设置计时器的过程,调用setTimeout()几乎完全不需要时间。也就是说,告诉系统"请在1000毫秒后调用此函数"将立即返回,因为在计时器队列中安装超时请求的过程非常快。

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

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

1
2
3
4
5
function doScaledTimeout(i) {
  setTimeout(function() {
    alert(i);
  }, i * 5000);
}

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

更新

在2018年,这里有一个更简单的选择。由于新的功能可以在比函数更窄的范围内声明变量,因此如果修改原始代码,则可以工作:

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

var不同,let声明本身会导致循环的每次迭代都有一个不同的i


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

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


这是因为!

  • 超时功能循环完成后,回调都运行良好。事实上,随着时间的推移,即使每次迭代都设置了超时(..0),所有这些函数回调在完成后仍将严格运行在循环中,这就是3被反射的原因!
  • 所有这两个函数,尽管它们是定义的在每个循环迭代中,分别在同一共享全局上关闭范围,实际上只有一个我在里面。
  • 解决方案是通过使用执行的自函数(匿名的一个或更好的IIFE)为每个迭代声明一个范围,并在其中包含一个i的副本,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for (var i = 1; i <= 2; i++) {

         (function(){

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

         })();

    }

    更干净的那个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for (var i = 1; i <= 2; i++) {

         (function(i){

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

         })(i);

    }

    在每个迭代中使用一个IIFE(自执行函数)为每个迭代创建了一个新的范围。迭代,这给了超时函数回调机会关闭每个迭代的新范围,其中一个迭代有一个变量其中包含每个迭代的权限值,供我们访问。


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

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

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


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

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

    注意:这在IE9及以下浏览器上不起作用。


    回答?

    我使用它来制作动画,将项目添加到购物车-购物车图标从产品"添加"按钮浮动到购物车区域,单击时:

    1
    2
    3
    4
    5
    6
    7
    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_u(参考日期)

    希望这有帮助!


    你可以用bind方法

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


    好吧,另一个基于科迪答案的工作解决方案是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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.

    一旦我解决了这个问题,我也有同样的问题。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        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


    真正的解决方案就在这里,但是您需要熟悉PHP编程语言。为了达到您的目的,您必须混合使用php和javascript命令。

    注意:

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

    它确实能满足你的需要,但是要小心如何在php变量和javascript变量。