在JavaScript中使用Loop的setTimeout

setTimeout with Loop in JavaScript

我有一个很小的问题。对于设置超时的简单循环,如下所示:

1
2
3
4
5
for (var count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count =" + count);
    }, 1000 * count);
}

控制台提供如下输出:

1
2
3
Count = 3
Count = 3
Count = 3

不知道为什么输出是这样的。有人能解释吗?


这与JavaScript中如何处理范围界定和提升有关。

您的代码中发生的事情是JS引擎将您的代码修改为:

1
2
3
4
5
6
7
var count;

for (count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count =" + count);
    }, 1000 * count);
}

setTimeout()运行时,它将首先在count之后查找它自己的作用域,但它找不到它,所以它将开始查找在setTimeout函数上关闭(这称为闭包)的函数,直到找到var count语句为止,它将具有值3,因为循环将在第一个t之前完成。已执行超时函数。

更多代码详细解释了您的代码实际上如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//first iteration
var count = 0; //this is 1 because of count++ in your for loop.

for (count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count =" + 1);
    }, 1000 * 1);
}
count = count + 1; //count = 1

//second iteration
var count = 1;

for (count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count =" + 2);
    }, 1000 * 2);
}
count = count + 1; //count = 2

//third iteration
var count = 2;

for (count = 0; count < 3; count++) {
    setTimeout(function() {
        alert("Count =" + 3);
    }, 1000 * 3);
}
count = count + 1; //count = 3

//after 1000 ms
window.setTimeout(alert(count));
//after 2000 ms
window.setTimeout(alert(count));
//after 3000 ms
window.setTimeout(alert(count));

这样想:

1000*N毫秒结束后,计数值是多少?

当然是3,因为foor循环的结束时间早于1000*n ms的超时时间。

要打印1、2、3,您需要以下内容:

1
2
3
4
5
6
7
8
9
for (var count = 0; count < 3; count++) {
    do_alert(num);
}

function do_alert(num) {
    setTimeout(function() {
        alert("Count =" + num);
    }, 1000 * num);
}

另一种方法是使其成为closure function(在javascript闭包和匿名函数中解释得很好)

1
2
3
4
5
for (var count = 0; count < 3; count++) {
    (function(num){setTimeout(function() {
        alert("Count =" + num);
    }, 1000 * num)})(count);
}

这两个代码示例实际上工作原理类似。

第一个示例在每次迭代中调用一个命名函数(do_alert)。

第二个示例在每次迭代中调用一个闭包匿名函数(就像do_alert)。

这都是范围问题。

希望有帮助。


这与闭包范围有关。每个setTimeout回调函数的作用域中都有相同的变量count。您正在增加它的值并创建一个函数,但是函数的每个实例在其作用域中都有相同的变量count,当回调函数执行时,它将具有值3。

您需要在for循环的新范围内创建变量的副本(例如var localCount = count),以使此工作正常。由于for不创建范围(这是整个过程的原因),所以需要引入一个具有函数范围的范围。

例如

1
2
3
4
5
6
7
8
9
for (var i = 0; i < 5; i++) {
  (function() {
    var j = i;
    setTimeout(function() {
      console.log(j)
    },
    j*100);
   })();
 }


想想看:

  • 代码执行一个循环,在这个循环中它设置一些代码稍后运行。
  • 循环结束。
  • 执行setTimeout代码。EDOCX1[0]的价值是什么?这个循环很久以前就结束了…

  • 首先,setTimeout(函数,毫秒)是一个函数,它需要在"毫秒"毫秒之后执行一个函数。

    记住,JS将函数视为对象,因此for(…)循环最初将生成如下内容:

    setTimeout( ... )
    setTimeout( ... )
    setTimeout( ... )

    现在,setTimeout()函数将逐个执行。

    函数的作用域将尝试查找当前作用域中的count变量。如果失败,它将转到外部作用域并找到count,其值已经由for循环增加到3。

    现在,开始执行……第一个警报立即显示,毫秒为0,第二个警报在1000毫秒后显示,第三个警报在2000毫秒后显示。所有警报都显示Count = 3


    这里简单的解决方法是使用ES6 let局部变量。您的代码看起来几乎是相同的,除非它能满足您的期望:)

    1
    2
    3
    4
    5
    6
     for (let count = 0; count < 3; count++) {
        setTimeout(function() {
            alert("Count =" + count);
        }, 1000 * count);

    }

    或者您可以创建一个递归函数来完成该任务,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function timedAlert(n) {
      if (n < 3) {
        setTimeout(function() {
            alert("Count =" + n);
            timedAlert(++n);
        }, 1000);
      }
    }

    timedAlert(0);


    更好的解决方案是在这种情况下"忘记循环和递归",并使用"setInterval"的组合包括"setTimeout"的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
        function iAsk(lvl){
            var i=0;
            var intr =setInterval(function(){ // start the loop
                i++; // increment it
                if(i>lvl){ // check if the end round reached.
                    clearInterval(intr);
                    return;
                }
                setTimeout(function(){
                    $(".imag").prop("src",pPng); // do first bla bla bla after 50 millisecond
                },50);
                setTimeout(function(){
                     // do another bla bla bla after 100 millisecond.
                    seq[i-1]=(Math.ceil(Math.random()*4)).toString();
                    $("#hh").after(''+i + ' : rand= '+(Math.ceil(Math.random()*4)).toString()+' > '+seq[i-1]);
                    $("#d"+seq[i-1]).prop("src",pGif);
                    var d =document.getElementById('aud');
                    d.play();                  
                },100);
                setTimeout(function(){
                    // keep adding bla bla bla till you done :)
                    $("#d"+seq[i-1]).prop("src",pPng);
                },900);
            },1000); // loop waiting time must be >= 900 (biggest timeOut for inside actions)
        }

    ps:理解(setTimeout)的实际行为:它们都将在同一时间开始,"三个bla bla将在同一时间开始倒数",因此请设置不同的超时来安排执行。

    ps 2:计时循环的例子,但是对于一个反应循环,您可以使用事件,promise async wait.。


    这是因为在for循环完成执行时,计数为3,然后调用set timeout。

    试试这个:

    1
    2
    3
    4
    5
    6
    var count = 0;
    setTimeout(function() {
           for (count = 0; count < 3; count++) {
               alert("Count =" + count);
            }
    }, 1000* count);

    这是因为所有超时都在循环结束时运行。

    然后,超时函数获取当前的count值。

    这总是3,因为for循环已经完成。