setTimeout的1000ms只是最早可执行时间,实际执行需等待当前同步代码、微任务全部完成且主线程空闲;若被2000ms长任务阻塞,则回调延迟至2000ms后,叠加调度开销。

因为 setTimeout 的 1000ms 只是“最早可执行时间”,不是“必定执行时间”。它必须等当前所有同步代码、当前宏任务内全部微任务都执行完,且主线程空闲后,才从宏任务队列中被取出运行。
主线程被长时间占用
JavaScript 是单线程的,同一时间只能执行一段代码。如果在调用 setTimeout 后,立刻运行一个耗时 2000ms 的同步任务(比如大循环、复杂计算或大量 DOM 操作),那么即使定时器早在 1000ms 时就“到期”,回调也只能排队等待——要等到那 2000ms 过完、主线程腾出手来,才会执行。
- 例如:
setTimeout(() => console.log('done'), 1000);后紧跟一个while(Date.now() - start < 2000) {},实际输出一定晚于 2000ms - 这种阻塞会让延迟直接“叠加”,变成 2000ms + 调度开销
微任务持续抢占执行权
每个宏任务结束后,引擎会清空整个微任务队列(Promise.then、queueMicrotask、MutationObserver 等)。如果页面正高频触发微任务(比如连续 resolve 大量 Promise),它们会不断插入并被立即执行,从而推迟下一个宏任务(包括你的 setTimeout 回调)的开始时机。
- 哪怕只延迟 0ms:
setTimeout(fn, 0)也可能比Promise.resolve().then(fn)晚几十毫秒 - 微任务不中断、不暂停,一连串下来,足以把 1000ms 定时器往后推
浏览器节流与系统限制
当页面不在前台(比如切换到其他标签页或最小化窗口),主流浏览器(Chrome、Edge、Firefox)会主动限制定时器精度,把最小间隔拉高到约 1000ms。此时设 setTimeout(fn, 1000) 可能实际延迟 1500ms 甚至更久;设 setTimeout(fn, 100) 则可能直接拖成 1000ms+。
立即学习“Java免费学习笔记(深入)”;
- HTML5 规范也规定:嵌套超过 5 层的 setTimeout,默认最小延迟为 4ms(但这是下限,不是上限)
- 系统负载高、CPU 占用满、后台进程多,也会间接拉长事件循环轮转周期
如何验证真实延迟
别信设定值,用 performance.now() 测绝对时间差:
const start = performance.now();<br>setTimeout(() => {<br> console.log('实际延迟:', (performance.now() - start).toFixed(1), 'ms');<br>}, 1000);这样得到的数值,才反映真实调度结果。你会发现,只要页面有动静,1000ms 很少刚好命中。


















