setTimeout 为什么不能保证及时执行

理解 JavaScript 事件循环机制和 setTimeout 的执行时机

问题

为什么 setTimeout 设置的延迟时间不能保证准时执行?

解答

setTimeout 的执行时机取决于 JavaScript 的事件循环机制(Event Loop)和主线程的繁忙程度。

执行流程

当浏览器的 JS 引擎遇到 setTimeout 时:

  1. 定时器注册setTimeout 被 timer 模块接管,不会立即放入任务队列
  2. 同步任务执行:主线程继续执行同步代码
  3. 等待时间到达:timer 模块在设置的延迟时间后,将回调函数放入异步任务队列
  4. 等待执行栈清空:只有当主线程的执行栈为空时,才会从任务队列中读取任务
  5. 执行回调:回调函数被放入执行栈并执行
console.log('开始');

setTimeout(() => {
  console.log('setTimeout 回调');
}, 100);

// 模拟耗时操作
for (let i = 0; i < 1000000000; i++) {}

console.log('结束');

// 输出顺序:
// 开始
// 结束
// setTimeout 回调(实际延迟远超 100ms)

为什么会延迟

即使设置了 100ms 的延迟,实际执行时间可能更长,因为:

  • 同步任务可能耗时很长,阻塞主线程
  • 任务队列中可能有其他待执行的任务
  • setTimeout 的回调必须等待执行栈完全清空

关键点

  • setTimeout 只保证在指定时间后将回调放入任务队列,不保证立即执行
  • 回调的实际执行时间 = 设置的延迟时间 + 主线程空闲等待时间
  • Event Loop 机制:执行栈清空后才会从任务队列中读取任务
  • 长时间运行的同步任务会阻塞 setTimeout 的执行