为什么JavaScript中setTimeout函数在循环内无法正常工作?
setTimeout在for循环中仅等待一次就执行所有回调的原因及解决办法
原因
JavaScript是单线程语言,遵循事件循环机制:同步任务会优先执行,异步任务(比如setTimeout的回调)会被放入任务队列,等同步任务全部完成后才会依次执行。
你的for循环是同步代码,会在瞬间执行完5次迭代,每次迭代都把一个setTimeout任务放进队列,且每个任务的延迟都是1000ms。所以1秒后,所有队列里的回调会被一次性取出执行,看起来就像只等了一次就全跑完了。
另外注意:你代码里用了let声明i,它在for循环的每次迭代中都会创建一个独立的块级作用域,所以最终console.log(i)会输出0、1、2、3、4(如果用var的话,所有回调都会拿到循环结束后的i=5),但这和“同时执行”的问题无关——不管用let还是var,所有回调都是1秒后同时触发。
解决办法
方法1:给每个setTimeout设置递增的延迟时间
把延迟时间改成i * 1000,让每个回调的触发时间依次错开:
for (let i = 0; i < 5; i++) { setTimeout(function(){ console.log(i); }, i * 1000) }
这样第一个回调0秒后执行,第二个1秒后,以此类推,实现每隔1秒输出一个数。
方法2:使用闭包(针对var声明的场景)
如果是用var声明i(老代码场景),可以用闭包保存每次迭代的i值,同时配合递增延迟:
for (var i = 0; i < 5; i++) { (function(index) { setTimeout(function(){ console.log(index); }, index * 1000) })(i) }
方法3:使用forEach循环
forEach的回调函数会为每个元素创建独立作用域,同样可以配合递增延迟:
[0,1,2,3,4].forEach(function(i) { setTimeout(function(){ console.log(i); }, i * 1000) })
方法4:用async/await实现串行执行
如果需要严格等待上一个回调执行完再触发下一个,可以用Promise包装setTimeout,配合async/await:
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } async function run() { for (let i = 0; i < 5; i++) { await delay(1000) console.log(i) } } run()
这种方式会真正等待1秒后输出一个数,再等1秒输出下一个,而不是一次性设置所有延迟。
内容的提问来源于stack exchange,提问作者JUAN JOSE HUERTAS BOTACHE
相关产品推荐
相关产品推荐

