如题,一般按照我们的思路:
1.写一个循环,循环5次,打印这个数
2.每隔一秒打印一次,用到setTimeout
于是,我们很容易写出
for( var i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000)
}
最后我们发现,打印出了 5 5 5 5 5
为什么呢,这个题其实考察的就是js中的事件循环了,这个之前我写过一篇文章,详见【深入理解 事件循环event loop 这一篇就够了】因为我们的事件循环执行顺序是【先同步,后异步,先微任务,再宏任务
】,因为for循环是立即执行的同步任
务,而var又不存在块级作用域,当进行for循环时,将 i 传递给setTimeout,因为setTimeout回调函数是异步宏任务,会放到任务队列中
,等到主线程任务
for循环 执行完毕再执行
,所以当console.log(i)执行的时候,使用var声明的迭代变量被设置为使循环退出的 i值
,此时 i 已经变成5了,就会打印5 个5
解决办法:
1.使用自执行函数
每次都把函数传递进去,这样就能保存每一次循环的 i ,相当于每一次循环都调用这个函数,把i传递进去,打印的自然就是每次循环的i
for(var i = 0; i <= 5; i++) {
(function(i) {
setTimeout(() => {
console.log(i)
}, 1000 * i)
} )(i)
}
相当于创建一个函数,然后再调用一个函数
for (var i = 0; i <= 5; i++ ) {
function temp(i) {
setTimeout(() => {
console.log(i)
}, 1000 * i)
}
temp()
}
2.使用let变量
我们已知,使用var定义的迭代器变量会溢出循环体
,但使用let声明的迭代器变量作用仅限于当前for循环
。
因为let是块级作用域
,js每循环一次就会声明一个新的迭代器变量
,所以每次setTimeoiut时引用的都是独立的迭代器变量实体,也就是说它将其重新绑定到循环体的每一次迭代中
,确保上一次迭代结束后每次都能被重新赋值。
setTimeout里面的函数为一个新的域
,var声明的变量是传递不到里面的,所以使用var最后打印的是退出循环的值为5,但使用let声明变量会在当前 这个块级作用域(for 循环的 body 体,也即两个花括号之间的内容区域),创建一个文法环境(Lexical Environment),该环境里面包括了当前 for 循环过程中的 i,setTimeout里的函数是可以使用这个变量i的。
那最后打印的就是每次执行循环迭代器的值: 1 2 3 4 5
for(let i = 1; i <=5; i++) {
setTimeout(() => {
console.log(i)
}, 1000 * i)
}
相当于
for (let i = 1; i <= 5; i++) {
let index = i;
setTimeout(() => {
console.log(index);
}, i * 1000)
}
3.setTimeout第三个参数
我们已知,setTimeout语法是这样的:
1.fn: (必传)需要执行的函数
2.time:(非必传)
传值时:倒计时time毫秒后执行fn
不传时:默认为0,fn在最早可以执行的时候执行,在任务队列的尾部执行fn,因此要等到同步任务和任务队列所有事件都执行完毕的时候再去执行fn
3.param: (非必传) fn的参数
param会作为回调函数的第一个参数传入
,所以我们把参数i传递进去就好~
for(var i = 1; i <= 5; i++ ) {
setTimeout((i) => {
console.log(i)
},
i * 1000,
i)
}