为何嵌套setTimeout与合并延迟的setTimeout执行顺序因总延迟不同而变化
为何嵌套setTimeout与合并延迟的setTimeout执行顺序因总延迟不同而变化
这问题我刚在Chrome里跑了一遍,确实和你说的结果一样,挺有意思的~本质原因是浏览器的setTimeout并不是绝对精确的定时器,它的实际触发时间会被好几个因素影响,咱们一步步拆解:
首先看你的代码逻辑:两组测试都是对比「嵌套两个定时器的总延迟」和「直接设置总延迟的单个定时器」,理论上总延迟相同应该同时执行,但实际顺序却不一样。
先聊50ms那组的情况(40+10 vs 50ms)
- 初始时,你设置了两个定时器:一个40ms后执行的外层定时器,和一个50ms后执行的单个定时器。
- 当时间走到「初始时间+40ms」时,外层定时器的回调执行,里面又设置了一个10ms的定时器——这个内部定时器的预期到期时间是「初始时间+40+10=50ms」,和直接设置的50ms定时器预期时间完全一致。
- 但实际执行时,Chrome的定时器调度机制会把这两个到期时间相同的回调,按照「新创建的定时器优先入队」的逻辑处理,你会先看到
Timeout1 (40+10ms),再看到Timeout2 (50ms)。
再看30ms那组的情况(20+10 vs 30ms)
这组的差异来自系统时钟的精度和微小的执行耗时:
- 同样,初始时设置20ms外层和30ms单个定时器。
- 当时间走到「初始时间+20ms」时,外层回调开始执行——哪怕这个回调本身只消耗了0.1ms的时间,此时当前时间已经变成了「初始时间+20.1ms」,这时候设置的10ms内部定时器,实际到期时间就变成了「20.1+10=30.1ms」。
- 而直接设置的30ms定时器,到期时间是「初始时间+30ms」,比内部的早了0.1ms,所以它的回调会先被加入宏任务队列,先执行,你就先看到
Timeout2 (30ms),再看到Timeout1 (20+10ms)。
总结一下核心原因
- 浏览器的
setTimeout的延迟是最小延迟,不是精确延迟:回调只会在至少指定延迟后执行,但实际触发时间会受事件循环的任务排队、系统时钟精度影响。 - 当总延迟较小时(比如30ms),哪怕是微秒级的执行耗时,都会让嵌套定时器的实际总延迟超过直接设置的总延迟,导致顺序反转;而当总延迟较大时(比如50ms),这种微小耗时的影响可以忽略,两个定时器几乎同时到期,浏览器的调度策略会让嵌套的内部回调先执行。
最后要提醒一句:这种执行顺序是浏览器实现相关的,不同浏览器甚至同一浏览器的不同版本都可能有差异,绝对不要依赖这种顺序来写业务代码哦!
备注:内容来源于stack exchange,提问作者Artem_3elt3er
相关产品推荐
相关产品推荐

