for循环+setTimeOut的经典面试问题

前段时间去面试的时候,遇到一道面试题,有关于for循环和setTimeOut的问题,其中还关乎var和let的区别

setTimeOut和setInterval的执行机制

在日常编码中,你会发现,给 setTimeout 和 setInterval 设定延迟时间往往并不准,或者干脆 setTimeout(function(){xxx},0) 也不是立马执行(特别是有耗时代码在前),这是因为 js 是单线程的,有一个事件队列机制,setTimeout 和 setInterval 的回调会到了延迟时间塞入事件队列中,排队执行。

setTimeout :延时 delay 毫秒之后,啥也不管,直接将回调函数加入事件队列。

setInterval :延时 delay 毫秒之后,先看看事件队列中是否存在还没有执行的回调函数( setInterval 的回调函数),如果存在,就不要再往事件队列里加入回调函数了。

如下例所示:

for(var i=0;i<3;i++){
	setTimeout(function(){
		alert(i);},1000)
	}

结果:在一秒之后,同时输出3个3

因为 for 循环会先执行完(同步优先于异步优先于回调),这时五个 setTimeout 的回调全部塞入了事件队列中,然后 1 秒后一起执行了。

面试题

分别求下面两个代码块的执行结果

for(var i=0;i<3;i++){
	setTimeout(function(){
		console.log(i);
	}
	,1000)
}

结果:5 5 5 5 5

for(let i=0;i<5;i++){
	setTimeout(function(){
		console.log(i);
	}
	,1000)
}

结果:隔一秒输出,值分别为0 1 2 3 4

这是因为第一个代码块中setTimeout 的 console.log(i); 的i是 var 定义的,所以是函数级的作用域,不属于 for 循环体,属于 全局变量。等到 for 循环结束,i 已经等于 5 了,这个时候再执行 setTimeout 的五个回调函数(参考上面对事件机制的阐述),里面的 console.log(i); 的 i 去向上找作用域,只能找到 全局作用下 的 i,即 5。所以输出都是 5。

而let是代码块的作用域,即是局部变量,所以每一次 for 循环,console.log(i); 都引用到 for 代码块作用域下的i,因为这样被引用,所以 for 循环结束后,这些作用域在 setTimeout 未执行前都不会被释放。

块级作用域和函数作用域的区别