for循环 + setTimeout 结合一些示例(前端面试题)
一、背景
最近在翻看以前的老书《node.js开发指南》,恰好碰到for循环+setTimeout的经典例子,于是重新梳理了思路并记录下。
二、写在前面,setTimeout和setInterval的执行机制
在日常编码中,你会发现,给setTimeout和setInterval设定延迟时间往往并不准,或者干脆setTimeout(function(){xxx},0)也不是立马执行(特别是有耗时代码在前),这是因为js是单线程的,有一个事件队列机制,setTimeout和setInterval的回调会到了延迟时间塞入事件队列中,排队执行。
setTimeout:延时delay毫秒之后,啥也不管,直接将回调函数加入事件队列。
setInterval:延时delay毫秒之后,先看看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不要再往事件队列里加入回调函数了。
看下面示例:
for(vari=0;i<5;i++){ setTimeout(function(){ console.log(i); },1000); }
结果:1秒之后,同时输出5个5。
因为for循环会先执行完(同步优先于异步优先于回调),这时五个setTimeout的回调全部塞入了事件队列中,然后1秒后一起执行了。
三、正文
接下来就是那道经典的代码:
for(vari=0;i<5;i++){ setTimeout(function(){ console.log(i); },1000); }
结果:55555
为什么不是12345,问题出在作用域上。
因为setTimeout的console.log(i);的i是var定义的,所以是函数级的作用域,不属于for循环体,属于global。等到for循环结束,i已经等于5了,这个时候再执行setTimeout的五个回调函数(参考上面对事件机制的阐述),里面的console.log(i);的i去向上找作用域,只能找到global下的i,即5。所以输出都是5。
解决办法:人为给console.log(i);创造作用域,保存i的值。
解决办法一
for(vari=0;i<5;i++){ (function(i){//立刻执行函数 setTimeout(function(){ console.log(i); },1000); })(i); }
这里用到立刻执行函数。这样console.log(i);中的i就保存在每一次循环生成的立刻执行函数中的作用域里了。
解决办法二
for(leti=0;i<5;i++){//let代替var setTimeout(function(){ console.log(i); },1000); }
let为代码块的作用域,所以每一次for循环,console.log(i);都引用到for代码块作用域下的i,因为这样被引用,所以for循环结束后,这些作用域在setTimeout未执行前都不会被释放。
四、补充
在写示例代码的过程中,发现一个语法点:
functiona(i){ console.log(i); } for(vari=0;i<5;i++){ setTimeout(a(i),1000); }
报错:
TypeError:"callback"argumentmustbeafunction atsetTimeout(timers.js:421:11) ……
百度了下,原来setTimeout不支持传带参数的函数,可以再用一个匿名函数包装下它吧,见下面代码:
functiona(i){ console.log(i); } for(vari=0;i<5;i++){ setTimeout(function(){//用匿名函数包装 a(i); },1000); }
总结
以上所述是小编给大家介绍的for循环+setTimeout结合一些示例(前端面试题),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!