这道笔试题如下
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函数块