浅谈循环中setTimeout执行顺序问题
(下面有见解一二)
期望:开始输出一个0,然后每隔一秒依次输出1,2,3,4。
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
}
结果:输出5。
原因:setTimeout 使函数延迟1s执行,而for循环执行完成还不到0.1秒,到执行函数的时候,其实 i 已经变成5了,因此console.log(i)输出5。
解决方法一:使用let块作用域。
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
}
解决方法二:加个闭包。
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
})(i);
结果:开始输出一个0,然后每隔一秒依次输出1,2,3,4。
失败方法:
for (var i = 0; i < 5; i++) {
(function() {
setTimeout(function() {
console.log(i);
}, 1000 * i);
})();
}
结果:输出 5。
原因:去掉函数的参数i,则函数内部没有对i保持引用。
解决方法三:
for (var i = 0; i < 5; i++) {
setTimeout((function(i) {
console.log(i);
})(i), i * 1000);
}
结果:立马输出0-4。
原因:setTimeout可以接受函数或者字符串作为参数,而给setTimeout传递了一个立即执行函数,该立即执行函数是undefined ,也就是说等价于setTimeout(undefined, ...)。立即执行函数会立即执行。
见解一:
for(var i=0;i<5;i++){
setTimeout(function(){
console.log(i);
},1000)
setTimeout是异步执行的,1000毫秒后向任务队列里添加一个任务,只有主线上的全部执行完才会执行任务队列里的任务,所以当主线程for循环执行完之后 i 的值为5,这个时候再去任务队列中执行任务,i全部为5;
每次for循环的时候setTimeout都会执行,但是里面的function则不会执行被放入任务队列,因此放了5次;for循环的5次执行完之后不到1000毫秒;
1000毫秒后全部执行任务队列中的函数,所以就是输出五个5啦
假如把var换成let,那么输出结果为0,1,2,3,4;
因为let i 的是区块变量,每个i只能存活到大括号结束,并不会把后面的for循环的 i 值赋给前面的setTimeout中的i;而var i 则是局部变量,这个 i 的生命周期不受for循环的大括号限制;
见解二:
for(var i=0;i<=3;i++){
setTimeout(function() {
console.log(i)
}, 10);
}
答案:打印4次4
这道题涉及了异步、作用域、闭包
settimeout是异步执行,10ms后往任务队列里面添加一个任务,只有主线上的全部执行完,才会执行任务队列里的任务,当主线执行完成后,i是4,所以此时再去执行任务队列里的任务时,i全部是4了。对于打印4次是:
每一次for循环的时候,settimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行,for循环了4次,就放了4次,当主线程执行完成后,才进入任务队列里面执行。
(注意:for循环从开始到结束的过程,需要维持几微秒或几毫秒。)
当我把var 变成let 时
for(let i=0;i<=3;i++){ setTimeout(function() { console.log(i) }, 10);}
打印出的是:0,1,2,3
当解决变量作用域,
因为for循环头部的let不仅将i绑定到for循环快中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了;这个匿名函数的参数作用域 和 for参数的作用域 不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。
查了一下百度的一个答案:
setTimeout是一次执行函数,这里是10ms后执行,仅仅执行一次;for(var i=0;i<=3;i++),i的每次取值都是执行setTimeout这个函数,并没有执行setTimeout里面的function(即闭包函数),setTimeout里面的function是有setTimeout的定时触动的,也就是10ms后执行,也就是说i从0~3时,一共执行了4次的setTimeout()函数,此时的i的值是4,由于for语句的执行速度远小于1秒,所以,1秒后,由setTimeout()函数定时触动的闭包函数function()开始执行,alert(i);i的值已经是4了,所以相继打印4次i.
---------------------