分享一道JavaScript闭包定时执行的笔试题

这道笔试题如下

var arr = ['第一次', '第二次', '第三次'];
 
for (var i=0; i < arr.length; i++) {
  setTimeout(function () {
    document.getElementById('info').innerHTML = arr[i];
  }, i * 10000);
}

问你执行结果。第一眼看到这道题,我当即就知道考的是闭包,于是我最快就直接说三次的arr[i]的值都是表示数组最后一个值,所以三次赋值结果都是第三次。时间是10秒第一次,20秒第二次,30秒第三次。

其实,这道题陷阱挺深,我也被陷进。回来做试验,三次结果都是undefined,并且从页面打开就先执行第一次,10秒后第二次,20秒后第三次。后来仔细缕缕思路才明白自己疏忽大意。

这道题的执行过程如下:

一共执行三次for块里的代码块

  • 第一次循环:i=0,所以第一次的时间设置实际上就等于0*10000 = 0
  • 第二次循环:i=1,所以第二次的时间设置实际等于 1*10000 = 10000 也就10秒后
  • 第三次循环:i=2,与第二次类似,20秒后

以上说明了时间实际是0秒、10秒、20秒的顺序执行了三个setTimeout定时函数。至于我为什么前面说它三次的结果都等于第三次呢?是因为i是全局变量,当for循环执行结束后i当然是最后一个数组长度,所以三次结果都是第三次。其实实际测试结果都是undefined,因为只有当i=3的时候才结束for循环,而arr[3]表示数组的第四个元素,实际arr数组里只有三个元素,所以arr[3]也就等于undefined

给大家做一个例子

for (var i=0; i < 3; i++) {
  console.log('for:' + i);
}
 
console.log('global:' + i);

这道题的执行结果:

for:0
for:1
for:2
global:3

而三次setTimeout最后能访问到 i 的值都是global的值,所以三次执行结果自然就是undefined

第二点,使用闭包的方式解决

JavaScript闭包我之前就写过一篇文章参见《JavaScript闭包访问外部变量解决实例》,我就把解决方案的代码给贴上

var arr = ['第一次', '第二次', '第三次'];
 
for (var i=0; i < arr.length; i++) {
  (function(num){
    setTimeout(function () {
      document.getElementById('info').innerHTML = arr[num];
    }, num * 10000);
  })(i);
}

这里需要说明一下,大家实际做开发时闭包千万不要滥用,因为闭包的作用域很大,因此占用内存堆栈比较多影响也面执行效率。

关于setTimeout执行流的问题,可能有些童鞋还是不太明白。我也顺手写了一个例子

var arr = [];
 
arr.push(1);
setTimeout(function () {
  arr.push(2);
}, 0);

arr.push(3);
setTimeout(function () {
  arr.push(4);
}, 0);

arr.push(5);

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

Output:

 [1, 3, 5, 2, 4]

从以上执行结果,我可以分析到虽然setTimeout得时间设置为0,但setTimeout需要等代码流执行结束后再来执行setTimeout函数块

分享

TITLE: 分享一道JavaScript闭包定时执行的笔试题

LINK: https://www.qttc.net/303-javascript-share-about-closure-questions.html

NOTE: 原创内容,转载请注明出自琼台博客