前几天无意看到一篇关于闭包的文章,我觉得对我启发还是比较大,所以记录下来。
先来看看代码
for(var i = 0; i < 5; i++){
setTimeout(function(){
console.log(i)
},1000)
}
console.log(i)
上面代码对还没对JS 中同步和异步代码的区别、变量作用域、闭包等理解的同学可能会给出答案是: 0,1,2,3,4,5 或 5,0,1,2,3,4
正确答案是:5,5,5,5,5,5。为什么呢?
下面先说一下主线程和异步线程,上述代码会先执行主线程,等执行完主线程再执行异步线程(setTimeout就是异步线程)。当主线程执行完,此时的 i 已经是5了。所以输出是5,5,5,5,5,5。
接下来再深入一点,输出的时间间隔( >表示1秒后 ,表示其前后的两次输出之间的时间间隔可以忽略)。
此时又有两个答案的出现分别是:5>5>5>5>5>5 和 5>5,5,5,5,5
真确答案是第二个:5>5,5,5,5,5(先输出5,1秒后输出5个5),为什么呢?
此时又要说一下同步和异步了。同步代码执行是要按顺序执行,如果其中一行代码有阻塞,后面的代码就会一直等待。异步代码执行,如果有代码阻塞,先会跳过阻塞代码继续执行后面代码。(举个例子:冲咖啡发现没热水,你会烧一壶热水再冲咖啡。重点来了,同步就是烧热水的过程你在一直等待,等到热水烧开再冲咖啡;异步就是在烧热水的过程中你又去做了其他的事情,热水烧开后再回去冲咖啡);看明白我说的例子相信你很快就会明白一秒后输出5个5了。因为setTimeout是异步执行,不会因前一个定时器而影响到后面的定时器执行,所以一秒后都会输出5。
下面又有一个问题了,怎么才能让代码按 5>0,1,2,3,4 这样的顺序输出呢?
了解过ES6的同学很快就想出使用let(块级作用域)代替var。
for(let i = 0; i < 5; i++){
setTimeout(function(){
console.log(i)
},1000)
}
console.log(i)
上述代码当你打开控制台检查会出现报错,为什么报错呢?因为第6行的 i 不在循环内部,i 是没有定义的,所以报错(使用let的原因,不明白建议了解一下let)。这是第一个方法,但是还有点缺陷。
第二个方法利用 IIFE(Immediately Invoked Function Expression:声明即执行的函数表达式)来解决闭包造成的问题。
for(var i = 0; i < 5; i++){
(function(j){
setTimeout(function(){
console.log(j)
},1000)
})(i)
}
console.log(i)
第三个方法利用 JS 中基本类型(Primitive Type)的参数传递是按值传递(Pass by Value)的特征解决问题。
var result = function(i){
setTimtout(function(){
console.log(i)
},1000)
}
for(var i = 0; i < 5; i++){
result(i) // 这里传过去的 i 值被复制了
}
console.log(i)
接下来又进一步加大难度,怎么才能立即输出0,然后1到5都每隔一秒才输出(新的需求可以精确的描述为:代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,循环结束后在大概第 5 秒的时候输出 5)
简单粗暴的方法:
for(var i = 0;i < 5;i++){
(function(j){
setTimeout(function(){
console.log(j)
},1000*j)
})(i)
}
setTimeout(function(){
console.log(i) //添加一个定时器,代码异步执行。
},1000*i)
简单粗爆的方法虽然有效,但说到异步是否还有更好的选择呢?对了,就是Promise。
const tasks = [];
for(var i =0; i <5; i++){
((j) => {
tasks.push(new Promise((resolve) => {
setTimeout(() => {
console.log(i)
resolve()
},1000*j)
}))
})(i)
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(i)
},1000) // 注意这里只需要把超时设置为 1 秒
})
我们还可以对以上代码简洁书写一下
const tasks = []
const output = (i) => new Promise((resolve) => {
setTimeout(() => {
console.log(new Date, i);
resolve();
}, 1000 * i);
});
for(var i =0; i < 5;i++){
tasks.push(output(i))
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(i)
},1000)
})
本章就记录到这里了,有错误的地方欢迎大家指出,谢谢!!