闭包的一道程序题

前几天无意看到一篇关于闭包的文章,我觉得对我启发还是比较大,所以记录下来。
先来看看代码

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)
})

本章就记录到这里了,有错误的地方欢迎大家指出,谢谢!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值