1. 为什么如下代码会打印 6 个 6
let i = 0
for(i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
先通俗的讲,上面代码执行时,先是for循环执行完,而setTimeout()内的回调函数在for循环执行完后执行6次,这个时候用来计数的i已经等于6了,所以会在控制台打印出6个6。
可是为什么setTimeout()能做到这一点?在网上查了一下,原因很复杂。
首先是因为没能正确理解setTimeout()。我们先来看下面的这段代码:
setTimeout(function(){
console.log("here");
}, 0);
var i = 0;
//具体数值根据你的计算机CPU来决定,达到延迟效果就好
while (i < 900000000) {
i++;
}
console.log("test");
结果为在过了一段时间之后,先打印了test,然后才是here。而且需要注意的是,上面的代码写的是setTimeout(..,0),如果按照之前错误地将setTimeout函数理解为延迟一段时间执行,那这里把时间赋为0岂不是马上执行了?而实验结论则印证了“setTimeout的意思是传递一个函数,延迟一段时间把该函数添加到队列中,并不是立即执行”的结论。(涉及到线程,异步,事件循环的知识我现在理解得还不到位,所以暂且不表)
再回到文章开头的代码,我们已经理解了console.log(i)在循环结束后执行6次。那为什么输出6呢?
跟闭包有关系。对闭包的解释:对函数类型的值进行传递时,保留对它被声明的位置所处的作用域的引用。
setTimeout()内的回调函数中的i并没有被声明,那就继续向外层的作用域找,我们可以看到i在最外层被声明。
console.log(i)中的i是全局作用域中的i,在循环结束后值为6,综上所述开头的代码会输出6个6。
2. 让上面代码打印 0、1、2、3、4、5 的方法
方法有很多,下面列举一些。
(1)将i的声明放入for循环内,利用let的特性
for(let i = 0; i < 6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
for 循环头部的let 声明还会有一个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
(2)引入IIFE(立即执行函数)
let i = 0
for(i = 0; i < 6; i++){
(function(i){
setTimeout(()=>{
console.log(i)
},0)
})(i)
}
(3)利用setTimeout第三个参数
let i = 0
for(i = 0; i < 6; i++){
setTimeout((j)=>{
console.log(j)
},0,i)
}
setTimeout()如果想给回调函数传递参数,直接在第二个参数delay后面加上附加的参数。
参考语法var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);
(4)利用闭包
let i = 0
for(i = 0; i < 6; i++){
setTimeout(((j) => {
return ()=>{
console.log(j)
}
})(i),0)
}