JavaScript 的事件循环机制是其实现非阻塞异步操作的核心。
核心概念
JavaScript 是单线程的,但通过 事件循环(Event Loop) 和 任务队列(Task Queue) 机制处理异步任务,确保主线程不被阻塞。事件循环通过协调 调用栈(Call Stack)、任务队列(宏任务、微任务) 和 Web APIs 的工作来实现这一点。
事件循环的运行流程
-
执行同步代码
主线程执行当前调用栈中的任务(如脚本整体代码、函数调用),遇到异步操作时,将其交给 Web APIs 处理(如setTimeout, AJAX 请求等)。 -
收集异步任务
- 宏任务(Macrotasks) :如
setTimeout、setInterval、DOM 事件回调、I/O 操作、requestAnimationFrame(浏览器)、setImmediate(Node.js)。 - 微任务(Microtasks) :如
Promise.then()、MutationObserver、process.nextTick(Node.js)。
- 宏任务(Macrotasks) :如
-
处理任务队列
- 微任务优先:当调用栈清空后,事件循环会先依次执行 所有微任务(直到微任务队列为空)。
- 执行一个宏任务:随后从宏任务队列中取出 一个任务 执行。
- 循环往复:重复此过程,形成事件循环。
关键特点
- 微任务优先级高于宏任务:每个宏任务执行后,必须清空微任务队列。
- 避免阻塞:长时间同步代码会阻塞事件循环,导致页面卡顿。
- 浏览器渲染时机:通常在宏任务之间执行 UI 渲染,但
requestAnimationFrame会在渲染前触发。
代码示例
javascript
复制
console.log("Start"); // 同步任务
setTimeout(() => console.log("Timeout"), 0); // 宏任务
Promise.resolve().then(() => console.log("Promise")); // 微任务
console.log("End"); // 同步任务
// 输出顺序:Start → End → Promise → Timeout
浏览器 vs Node.js 的事件循环
- 浏览器:以宏任务和微任务为核心,微任务在每次宏任务后执行。
- Node.js:事件循环分为多个阶段(如
timers、poll、check),每个阶段处理特定类型的宏任务,之后执行微任务(Node v11+ 后行为类似浏览器)。
总结
- 同步代码 → 微任务 → 一个宏任务 → (重复)→ 渲染(浏览器) 。
- 理解事件循环有助于编写高效、无阻塞的异步代码,避免性能问题。