for循环 + setTimeout 例子
今天看面试题找到了一个经典面试题,可以帮助大家理解let关键字,setimeout关键字等。
先看一个经典面试题
for (var i = 0; i < 6; i++) {
setTimeout(function (){
console.log(i);
},1000);
}
运行代码,一下子输出了五个5。并不是意料中的每隔1秒输出i++,这是因为var是全局作用域,而setimeout是一个异步函数,for循环又是一个同步执行的。记住一个口诀 同步 => 异步 => 回调,所以for循环先执行又因为i是用var定义的。所以同步执行完后i=5,接下来执行异步函数输出五个5
第一种解决方法
用自执行函数(立刻执行函数)传递i的参数
for( var i=0;i<6;i++){
(function(lockedIndex){
setTimeout(function(){
console.log(lockedIndex);
}
,300);
})(i);
}
// 输出 "1,2,3,4,5"
尽管循环执行结束,i值已经变成了3。但因遇到了自执行函数,当时的i值已经被 lockedIndex锁住了。也可以理解为 自执行函数属于for循环一部分,每次遍历i,自执行函数也会立即执行。所以尽管有延时器,但依旧会保留住立即执行时的i值.
简单理解当同步执行的时候把for循环i的值,传递给了延时器,让延时器也能保留i的值,当执行到setimeout时候也能打印出i的值
第二种解决方法
用关键字let的块级作用域
for (let i = 0; i < 6; i++) { //let 代替 var
setTimeout(function (){
console.log(i);
},1000);
}
这里我们可以先学习es6中的let
变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是1,2,3,4,5。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。所以就算是for和setTimeout()执行的顺序不变,但是打印输出的还是本次循环i的值.
简单理解就是JavaScript引擎会记住循环的结果,所以同步执行完成后异步setimeout也能记住i的值.