for循环与setTimeout的异常表现:两段代码为何结果不同?
为什么两段看似相同的代码输出顺序不同?
先看两个场景的代码和实际输出:
场景1
for (var i=0; i<3; ++i) { let num = i; setTimeout(function() { alert(num); }, 10); }
输出序列: 依次弹出 0, 1, 2
场景2
for (var i=0; i<3; ++i) { // 此语句中声明的变量作用于所在块级作用域 let num = i; setTimeout(function() { alert(num); }, 10); }
输出序列: 依次弹出 0, 2, 1
你肯定会纳闷:这两段代码除了一行注释,看起来完全一样,为啥弹出顺序差这么多?下面给你拆解清楚:
1. 先理清代码的基础逻辑
首先,var i是函数级作用域变量,整个循环过程中只有一个i的绑定。但每次循环的代码块里,let num = i会创建一个块级作用域的新变量绑定——也就是说,每一轮迭代的num都是独立的,回调函数会捕获各自的num值,所以三个回调分别对应0、1、2这三个值,这部分是两段代码共有的逻辑。
2. 输出顺序差异的核心原因
那为啥弹出顺序不一样?问题出在JavaScript事件循环的调度细节,以及JS引擎的执行优化:
- 在场景1中,循环是连续执行的,三个
setTimeout几乎同时被调度,延迟时间都是10ms,所以回调会按照被加入任务队列的顺序(0→1→2)依次执行,弹出顺序就是0、1、2。 - 场景2里的注释本身不影响代码逻辑,但它会让代码的解析、执行步骤产生极其细微的时间差——或者更准确地说,在某些JS引擎(比如特定版本的Chrome V8)中,当循环块内存在注释时,引擎的优化策略会发生变化:循环迭代的间隔被轻微拉长,导致第三个
setTimeout的回调反而比第二个更早进入任务队列,最终弹出顺序变成0、2、1。
要注意的是:这种差异属于JS引擎实现层面的细节,不是ECMAScript语言规范强制要求的行为。换一个浏览器或者JS引擎版本,场景2的输出可能和场景1完全一致。本质上,setTimeout的延迟时间只是「最小延迟」,不是「精确延迟」,回调的执行顺序在极端情况下可能因为引擎的调度优化出现波动。
如果把var i换成let i(让迭代变量也变成块级作用域),两段代码的输出顺序就会稳定一致,因为let i在每次迭代都会创建新的绑定,引擎的调度优化空间会更小。
内容的提问来源于stack exchange,提问作者Kathir
相关产品推荐
相关产品推荐

