question:setTimeOut的精确调用
setTimeout(fn, delay)
并不能保证回调函数一定会在 delay
毫秒后 “准时” 执行。
JavaScript 是单线程的,浏览器中有一个任务队列(Task Queue):
- 同步代码 → 直接在调用栈执行
- 微任务(Microtask) → Promise.then、MutationObserver 等,会在同步代码执行完立即执行
- 宏任务(Macrotask) → setTimeout、setInterval、I/O 回调、UI 渲染回调等,会在下一轮事件循环执行
setTimeout
的工作流程是:
- 你调用
setTimeout(fn, 1000)
,浏览器会在 约 1000ms 后把fn
推到宏任务队列 - 但队列里可能还有其他任务排在前面(比如一个耗时 500ms 的同步操作)
- 所以实际执行时间 = 延迟时间 + 前面任务的执行时间
注意:在纯浏览器 JS 环境中,无法绝对保证一个回调在精确的某个毫秒数执行,因为 JS 是单线程的,一旦线程被占用,定时器就会延迟。但是我们可以用一些方法尽量精确!
方法一:使用 requestAnimationFrame
(适合动画场景,会在浏览器每一次重绘前执行,通常是 60FPS,约 16ms 一次)
方法二:使用 setTimeout
+ 修正时间(自校准定时器)
这里注意:Date.now()
的精度是毫秒级,performance.now()
是微秒级(更高精度)
记录上一次执行的时间,计算下一次 “应该” 执行的时间,动态调整延迟。
let startTime = Date.now();
let count = 0;function tick() {count++;let expectedTime = startTime + count * 100; // 每 100ms 一次let realTime = Date.now();console.log('理想时间:', expectedTime, '实际时间:', realTime, '偏差:', realTime - expectedTime);// 下一次延迟 = 理想间隔 - 偏差let delay = Math.max(0, 100 - (realTime - expectedTime));setTimeout(tick, delay);
}tick();
方法三:使用 Web Worker
如果你的 JS 逻辑耗时很长,可以把耗时操作放进 Web Worker,这样主线程就不会被阻塞,setTimeout的那部分逻辑
会更准时
但是,浏览器 JS 无法 100% 保证定时精确,因为单线程 + 宏任务队列机制