彻底解释清楚Event loop

226 阅读3分钟

为什么需要 Event Loop?

如果一个机制存在,却没有解决任何问题,那么它就毫无意义。

JavaScript 是单线程的,main thread 一次只能做一件事。
可是浏览器要同时处理:用户点击、网络请求、脚本执行、UI 渲染……

这时,就必须要有一个统一的“调度员”来协调所有任务,它就是 Event Loop

虽然现代浏览器已经支持 Web Workers 可以开线程,但绝大多数逻辑依旧依赖 event loop 来驱动。

1、什么是 Event Loop?

很多人都听过 event loop,但标准怎么定义呢?

👉 规范 中写道:

为了协调事件、用户交互、脚本、渲染、网络请求等,用户代理必须使用 event loop。

换句话说:event loop 是浏览器的大脑,负责“调度和执行”一切。

Task & Microtask

  • 一个 event loop 有 一个或多个 task queues(任务队列)。
  • 每个 task 可能是事件、ajax 回调、DOM 操作、解析 HTML……
  • 还有一个 microtask queue(微任务队列),比如 Promise.then / MutationObserver。

👉 小细节:规范里特别提到,microtask 在特殊情况下可能会被转移到 task queue,比如在执行过程中“spin the event loop”。

2、浏览器 Event Loop 的整体架构

你可以把浏览器里 event loop 的运行流程想象成:

  1. JS 引擎只负责执行代码;
  2. Web APIs 负责定时器、DOM 事件、网络请求等;
  3. 任务队列(task queue)  收集异步完成的回调;
  4. event loop 就像中间的“调度员”,不断检查有没有任务需要执行。

来看个例子:

console.log('Hi');
setTimeout(function cb1() { 
    console.log('cb1');
}, 5000);
console.log('Bye');

执行顺序:

  • 首先输出 Hi
  • 注册一个定时器,把回调交给 Web APIs 等待;
  • 输出 Bye
  • 5 秒后,Web APIs 把 cb1 放入任务队列;
  • event loop 发现主线程空闲,就把 cb1 拿出来执行,输出 cb1

3、浏览器 Event Loop 执行步骤

浏览器里每一轮循环大致分为以下步骤:

  1. 从 task queue 取一个任务执行(例如 setTimeout 回调)。
  2. 执行 microtask checkpoint(例如 Promise.then 回调)。
  3. 尝试渲染 UI(但不是每次循环都会渲染)。
  4. 检查一些浏览器相关的任务(resize、scroll、media query 等)。
  5. 执行 requestAnimationFrame 的回调
  6. 重新计算样式和布局
  7. 如果有空闲时间,执行 requestIdleCallback

总结一句:先执行宏任务,再清空微任务,然后渲染和其他操作,循环往复。

4、Node.js 的 Event Loop

Node.js 的 event loop 和浏览器类似,但阶段划分更细。你可以把它理解为一个“分段式流水线”:

  1. timers 阶段:执行 setTimeout、setInterval 回调。

    • 注意:定时器的触发时间不一定精确。
  2. pending callbacks 阶段:执行一些系统级错误回调,比如 TCP 错误。

  3. poll 阶段

    • 如果队列不为空 → 执行所有 I/O 回调;
    • 如果为空 → 检查是否有 setImmediate,有则进入下一步,否则继续等 I/O。
  4. check 阶段:执行 setImmediate 回调。

  5. close callbacks 阶段:执行关闭事件,比如 socket.on('close')。

👉 关键点:

  • setTimeout 和 setImmediate 的先后取决于当前 event loop 所处阶段;
  • 这就是为什么 Node.js 里经常会有 “输出顺序题”。

5、为什么要理解 Event Loop?

因为它决定了:

  • 代码执行顺序:同步、异步、微任务先后。
  • 性能优化:比如知道 requestAnimationFrame 在渲染前调用,就能做更流畅的动画。
  • Bug 定位:比如为什么 setTimeout 延迟了,为什么 promise 回调先执行。

一句话:理解 event loop,就是理解 JavaScript 的运行时灵魂。

6、推荐学习资源

总结

浏览器的 event loop 更偏重于 协调 UI 渲染与任务调度
Node.js 的 event loop 更偏向于 IO 驱动的任务管理

无论哪种环境,搞清楚 event loop,才能写出更高效、更可靠的代码。