A

for (var i=5; i>=1; i--) { 
      setTimeout( function timer() {
          document.write(i+"<br />");
      }, i*3000 );
}
// 0 0 0 0 0
// 保存执行后,3秒后弹出第一个console(0),之后隔3秒弹出第二个,依次按照时间执行。

B

 for (var i=5; i>=1; i--) { 
      setTimeout( function timer() {
          document.write(i+"<br />");
      }, 3000 );
}
// 0 0 0 0 0 
// 保存执行后,3秒后所有的console一齐弹出

C

 for (var i=5; i>0; i--) { 
          !function (i) {
              setTimeout( function timer() {
                console.log(i);
              }, i*3000 );
          }(i)
    }
   // 1 2 3 4 5 每隔3秒依次弹出
   
   

这里我抽象一下setTimeout的队列执行结构:

图片描述

注意,时间设定与闭包没有关系!

当所有的非队列结构代码都执行完毕,函数调用栈清空后,这些setTimeout才会进入队列数据栈等待执行
。而时间,就是按照图中那样:

A:

A与B的区别就是时间i*30003000的区别。
图片描述
setTimeout定义是与正常的JS执行是同步的,而函数的执行是异步的,所以,根据不同的i值,setTiemout在时间轴上已经定义了函数。
而且可以发现,时间设置里的i是根据for循环变化的(5 4 3 2 1),这也反证了setTimeout的定义时是与主JS程序同步。所以根据不同的i*3000,在时间轴上定义匿名函数。执行时,每隔3秒,弹出一个0。
当然,由于匿名函数指向的是全局变量i,此时主JS执行结束早已为0,所以弹出0。

B:

在B代码中,没有设置i与时间的计算,所以循环中产生的5个匿名函数都是定义在3秒这个时间节点上,当主JS代码执行完毕,开始执行队列结构栈中的函数时,5个函数在3秒后同时执行,所以会弹出5个0。而对于匿名函数中的i取值,这里自然是来自全局变量中的i,主JS程序执行完毕i自然运算完毕值为0,所以5个i都是0;


C:

图片描述

引入闭包的情况和B其实是类似的,时间设定与匿名函数执行始终井水不犯河水,所以时间轴与B相同,不同的是匿名函数里的i值。
图中嵌套的正方形就抽象为自调用函数,每一次的循环中产生的i值,会传入自调用函数,保存在其变量对象中。由于匿名函数里的是自调用函数的闭包匿名函数引用了变量对象里的i,所以每个自调用函数的变量对象不会在执行完毕后被回收,得以保持继续被匿名函数引用着。最后实现最终效果。


Queen
139 声望20 粉丝