众所周知,js的函数执行结果会随着它的执行时机不同而不同。但下面的代码难免让人匪夷所思:
为什么如下代码会打印 6 个 6?
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
原因很简单,由于for循环的缘故,此代码中设定了6个setTimeout定时器。而因为 js 是单线程的,有一个事件队列机制,setTimeout 和 setInterval的回调会塞入事件队列中,排队执行。这就意味着定时器仅仅是计划代码在未来的某个时间执行,也就是执行完当前代码,才可能会开始执行定时器里面的代码。所以,当for循环执行完成后,i的值为6,再执行设定好的6个定时器,于是输出了6个6。
而下面的代码则会打印出0、1、2、3、4、5
for(let i=0;i<6;i++){
setTimeout(()=>{console.log(i)},2000)
}
这又是为什么呢?原因是let是块级作用域,let写在了for语句中,所以作用域是for代码块。所以每一次 for 循环,console.log(i); 都引用到 for 代码块作用域下的i,因为这样被引用,所以 for 循环结束后,这些作用域在 setTimeout 未执行前都不会被释放。
如果不相信,可以在for循环体外打印i;你会发现报错:
Uncaught ReferenceError: i is not defined。
也就是说,上面的代码相当于:
let i;
for(i=0;i<6;i++){
let temp=i;
setTimeout(()=>{console.log(temp)},2000)
}
那用var可不可以输出0、1、2、3、4、5?
答案是不可以。
for (var i = 0; i < 6; i++) {
setTimeout(function (){
console.log(i);
},1000);
}
因为因为 setTimeout 的 console.log(i); 的i是 var 定义的,所以是函数级的作用域,不属于 for 循环体,属于 global。等到 for 循环结束,i 已经等于 6了,这个时候再执行 setTimeout 的五个回调函数(参考上面对事件机制的阐述),里面的 console.log(i); 的 i 去向上找作用域,只能找到 global下 的 i,即 6。所以输出都是6。
如果你不相信,可以在for循环体外打印i,你会发现,i存在,且i=6。
除了使用 for let 配合,还有什么其他方法可以打印出 0、1、2、3、4、5?
用立即执行函数:
for (var i = 0; i < 6; i++) {
(function(i){ //立刻执行函数
setTimeout(function (){
console.log(i);
},1000);
})(i);
}
这样就把i的作用域放在for循环体里面了。执行结果为:0、1、2、3、4、5。