问题来源
for (var i = 0; i< 10; i++){
setTimeout((i) => {
console.log(i);
}, 0)
}
期望:输出1到10
为什么无法输出1到十
因为在es5中是没有块级作用域的,看起来上面的代码好像被括号包住了每个i应该都能输出,但是其实并不是这样。
模拟这段代码
var i=0; // 1
if(i<10) // 2
setTimeout(function(){console.log(i)},100) // 3
i++ // 4
if(i<10) // 5
setTimeout(function(){console.log(i)},100) // 6
i++ // 7
...直到i不满足if语句条件终止代码
因为es5中没有块级作用域,省略了括号看起来更直观。在代码中看起来应该好像是从上往下依次执行,但其实不是的。在js执行过程中,当同步和异步代码同时存在时,异步代码会在同步代码全部执行完成后再调用。而setTimeout就是异步代码(就算延迟为零也是异步),它会在代码最后执行,因此执行顺序为1>2>4>5>7>**>3>6。最后才执行setTimeout这时候的i已经经过自增变成了10,所以最后输出的结果为十个10.
setTimeout什么时候调用
var begin=new Date().getSeconds()
console.log('现在是第'+begin+'秒') // 1
setTimeout(function(){
console.log('setTimeout开始执行') // 2
},10)
while((new Date().getSeconds()-begin)<2){ //设置两秒间隔
continue
}
console.log('现在是第'+(new Date().getSeconds())+'秒') //3

在上面代码中我手动设置了一个两秒间隔的同步代码,而setTimeout的触发时间设置为10毫秒。如果按照从上到下依次执行的情况来看,执行顺序应该为1>2>3。但是实际却是1>3>2。这就佐证了我上面的看法,setTimeout在所有同步代码执行完后才调用。那你可能会问,如果不按时执行,那setTimeout中设置的触发时间不是没用了吗。那就涉及到优先级的问题了,setTimeout会在指定时间后触发。如果同步代码执行时间(或者其他在它之前执行异步代码)大于设定时间,那么它将在其他代码执行完成后立即执行。
想要了解异步执行顺序可阅读这篇文章:Js 的事件循环(Event Loop)机制以及实例讲解
解决方法
- 方法一
for (var i = 0; i< 10; i++){
setTimeout((i) => {
console.log(i)
}, 1000,i);
}
最精简解决方案
- 方法二
for (let i = 0; i< 10; i++){
setTimeout(() => {
console.log(i)
}, 1000);
}
最优解决方案,利用
let形成块级作用域
- 方法三
for (var i = 0; i< 10; i++){
((i)=>{
setTimeout(() => {
console.log(i)
},1000);
})(i)
}
IIFE(立即执行函数),类似于let生成了块级作用域。
- 方法四
for (var i = 0; i< 10; i++){
setTimeout(console.log(i),1000);
}
直接输出,没有延迟
- 方法五
for (var i = 0; i< 10; i++){
setTimeout((()=>console.log(i))(),1000);
}
同上
- 方法六
for (var i = 0; i< 10; i++){
try{
throw i
}catch(i){
setTimeout(() => {
console.log(i)
}, 1000)
}
}