settimeout 只保证“至少延迟多少毫秒”,实际执行受事件循环、主线程阻塞、系统调度等影响;其回调为宏任务,须等同步代码和微任务执行完才可能被调度,且存在最小延迟限制(如浏览器≥4ms)。

setTimeout 的延迟误差不是它“不准”,而是事件循环机制决定的——它只能保证“至少延迟多少毫秒”,不能保证“正好延迟多少毫秒”。真正执行时间取决于任务队列状态、主线程是否空闲,以及宏任务排队时机。
setTimeout 是宏任务,必须等当前执行栈清空
调用 setTimeout 时,只是把回调函数注册进一个定时器线程(浏览器/Node.js 管理),到时间后,回调被推入宏任务队列(Task Queue)。但只有当 JS 主线程上的同步代码和当前正在执行的微任务全部完成,事件循环才会从宏任务队列中取出一个任务执行。
- 如果 setTimeout(100) 被调用后,主线程立刻开始一段耗时 200ms 的 for 循环,那么回调最早也要在 300ms 后才可能执行
- 即使设为 0,setTimeout(fn, 0) 也绝不会在当前同步代码中立即执行,它一定排在本轮微任务(如 Promise.then)之后
定时器线程不精确,受系统调度与最小间隔限制
浏览器对 setTimeout 有最低延迟限制(通常 ≥4ms),尤其在页面非活跃或嵌套调用时会自动拉长。HTML5 规范规定:嵌套 5 层以上的 setTimeout,最小延迟强制为 4ms;Node.js 中虽无硬性限制,但受 libuv 定时器精度和系统时钟分辨率影响(如 Windows 默认约 15.6ms)。
- setTimeout(fn, 1) 在 Chrome 中实际可能延迟 4–16ms
- 页面切到后台时,多数浏览器会将定时器节流至 1s 左右,防止耗电——这时 100ms 的定时器可能拖到 1000ms+ 才触发
事件循环阶段决定“何时能取到”这个宏任务
一次事件循环包含:执行同步代码 → 清空微任务队列 → 检查宏任务队列(含 setTimeout 回调)→ 渲染(可选)→ 下一轮。setTimeout 回调只有在“检查宏任务队列”这一步才可能被选中,且每次只取一个。
- 若同一轮中已有其他宏任务(如 I/O 回调、用户点击事件),setTimeout 回调需排队等待
- 连续调用多个 setTimeout,它们进入宏任务队列的顺序是确定的,但执行时机仍取决于每轮循环时主线程是否空闲
需要高精度?别依赖 setTimeout
对动画帧、音频同步、实时倒计时等场景,setTimeout 天然不适合。应改用 requestAnimationFrame(针对视觉更新)、Web Workers(避免阻塞主线程)、Performance.now() + 自循环校准,或使用 setInterval 配合时间差修正(注意内存泄漏风险)。
- 用 performance.now() 记录计划时间和实际执行时间,动态调整下一次 delay
- 高频定时需求优先考虑 Web API 如 AudioContext 或 WebRTC 的 timing APIs
- Node.js 中可使用 setImmediate(仅旧版)或 queueMicrotask + Date.now() 做更细粒度控制
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











