又被问到 js 事件循环

106 阅读2分钟

JavaScript 的事件循环机制是其实现非阻塞异步操作的核心。


核心概念

JavaScript 是单线程的,但通过 事件循环(Event Loop)  和 任务队列(Task Queue)  机制处理异步任务,确保主线程不被阻塞。事件循环通过协调 调用栈(Call Stack)、任务队列(宏任务、微任务)  和 Web APIs 的工作来实现这一点。


事件循环的运行流程

  1. 执行同步代码
    主线程执行当前调用栈中的任务(如脚本整体代码、函数调用),遇到异步操作时,将其交给 Web APIs 处理(如 setTimeout, AJAX 请求等)。

  2. 收集异步任务

    • 宏任务(Macrotasks) :如 setTimeoutsetInterval、DOM 事件回调、I/O 操作、requestAnimationFrame(浏览器)、setImmediate(Node.js)。
    • 微任务(Microtasks) :如 Promise.then()MutationObserverprocess.nextTick(Node.js)。
  3. 处理任务队列

    • 微任务优先:当调用栈清空后,事件循环会先依次执行 所有微任务(直到微任务队列为空)。
    • 执行一个宏任务:随后从宏任务队列中取出 一个任务 执行。
    • 循环往复:重复此过程,形成事件循环。

关键特点

  • 微任务优先级高于宏任务:每个宏任务执行后,必须清空微任务队列。
  • 避免阻塞:长时间同步代码会阻塞事件循环,导致页面卡顿。
  • 浏览器渲染时机:通常在宏任务之间执行 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:事件循环分为多个阶段(如 timerspollcheck),每个阶段处理特定类型的宏任务,之后执行微任务(Node v11+ 后行为类似浏览器)。

总结

  • 同步代码 → 微任务 → 一个宏任务 → (重复)→ 渲染(浏览器)
  • 理解事件循环有助于编写高效、无阻塞的异步代码,避免性能问题。