JS 函数的执行时机
for 里面的 setTimeout
let i = 0
for(i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
}, 10)
}
上面是一个普通的 for 循环,不同的是,里面有个 setTimeout 计时器。
我想实现的效果是每 10 毫秒打印一下 i 的值。
接下来运行一下看看啥效果。
// 6
// 6
// 6
// 6
// 6
// 6
怎么打了 6 个 6,而不是从 0 到 5,这是咋回事呢?
重点在于 for 循环里面的 setTimeout,setTimeout 里的语句想等到其他语句执行完毕后再执行,就比如要等到 for 循环结束的时候再去打印。
for 循环执行完毕后,计时器里面代码才开始运行,循环了 6 次,这时候 i 的值为 6,,结果就是 6 个 6 咯。
闹钟的比喻
还没有懂吗?做个比喻好了,就好比 for 循环遇到 setTimeout 时,定了一个 10 毫秒的闹钟。6 次循环,定了 6 个闹钟。
但由于是同时定了 6 次 一样时间的闹钟,时间一到,就会叮铃铃的响 6 次。但这里 for 循环的值已经是 6 了,就和闹钟的时间一样,过了一分钟,现在是 12:00,每个闹钟当然都会显示 12:00 嘛。
我想要的不是这样的效果
for(let i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
}, 10)
}
想要从 0 到 5,简单。for 循环里面加个 let 就行。
也许是 JS 设计者认为这个特性让人感到有些费解,for 循环里的 let 出来的 i 在 setTimeout 里循环出来就变得非常符合直觉。`
// 0
// 1
// 2
// 3
// 4
// 5
对,这就是想要的效果。
更多的答案
用一个闭包也能解决问题,看起来有点绕。
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
这里的闭包就是匿名函数和函数外的 i。
for (var i = 0; i < 6; i++) {
(function(j){
setTimeout(function(){
console.log(j);
},4000)
})(i);
};
// 0
// 1
// 2
// 3
// 4
// 5
实际上上面的代码是将 i 每一次循环的值都记录了下来,i 变一次,函数执行一次。 最终执行 6 次的效果就是我们想要的效果
最后
聪明的你也许知道我什么讲的是什么,如果说平时写的代码是同步代码,从上运行到下。那么 setTimeout 其实是一种异步代码,也就是等一下,不着急么,大家完事了我再来。