前置知识: JavaScript

事件循环详解

2 minAdvanced2026/6/14

JavaScript事件循环深度解析:宏任务与微任务优先级、浏览器与Node.js差异。

1. 事件循环模型

1.1 浏览器事件循环

┌───────────────────────────┐
│        Call Stack          │
│  ┌─────────────────────┐  │
│  │   执行上下文 (LIFO)  │  │
│  └─────────────────────┘  │
└──────────┬────────────────┘

     ┌─────┴──────┐
     │  Event Loop │ ← 持续检查
     └─────┬──────┘

   ┌───────┴────────┐
   │                 │
┌──┴───┐        ┌───┴────┐
│ Micro │        │  Macro  │
│ task  │        │  task   │
│ Queue │        │  Queue  │
└──────┘        └────────┘

1.2 执行流程

1. 执行全局同步代码(宏任务)
2. 执行栈清空后,检查微任务队列
3. 清空所有微任务(包括执行中新增的微任务)
4. 取一个宏任务执行
5. 重复步骤 2-4

2. 宏任务与微任务

2.1 分

宏任务(Macro Task)微任务(Micro Task)
<script> 整体代码Promise.then/catch/finally
setTimeout / setIntervalMutationObserver
setImmediate(Node.js)queueMicrotask()
I/O 回调process.nextTick(Node.js)
UI 渲染IntersectionObserver
requestAnimationFrame

2.2 优先级

微任务优先级(从高到低):
  process.nextTick(Node.js)
  Promise.then
  queueMicrotask
  MutationObserver

宏任务优先级(从高到低):
  setImmediate(Node.js)
  setTimeout / setInterval
  I/O
  setImmediate(浏览器,部分支持)

2.3 经典面试题

console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => console.log('3'));
}, 0);

Promise.resolve().then(() => {
  console.log('4');
  setTimeout(() => console.log('5'), 0);
});

console.log('6');

// 输出顺序:1, 6, 4, 2, 3, 5

解析:

第一轮(宏任务:全局代码):
  同步: 1, 6
  微任务队列: [Promise.then → 4]
  宏任务队列: [setTimeout → 2]

第二轮(清空微任务):
  执行: 4
  新增宏任务: [setTimeout → 5]
  微任务队列: 空

第三轮(下一个宏任务: setTimeout → 2):
  同步: 2
  新增微任务: [Promise.then → 3]

第四轮(清空微任务):
  执行: 3

第五轮(下一个宏任务: setTimeout → 5):
  同步: 5

3. 微任务的递归问题

3.1 微任务饥饿

//  微任务递归会阻塞宏任务
function recursiveMicrotask() {
  Promise.resolve().then(recursiveMicrotask);
}
recursiveMicrotask();
// setTimeout 永远无法执行!

//  使用 setTimeout 让出执行权
function recursiveTask() {
  setTimeout(recursiveTask, 0);
}
recursiveTask();
// 其他任务有机会执行

3.2 微任务数量控制

async function processLargeArray(items) {
  for (let i = 0; i < items.length; i++) {
    processItem(items[i]);

    // 每 100 项让出一次执行权
    if (i % 100 === 0) {
      await new Promise((resolve) => setTimeout(resolve, 0));
    }
  }
}

4. Node.js 事件循环

4.1 阶段模型

   ┌──────────────────────────┐
┌─>│        timers             │  ← setTimeout/setInterval
│  └─────────────┬────────────┘
│  ┌─────────────┴────────────┐
│  │     pending callbacks     │  ← 系统级回调(TCP 错误等)
│  └─────────────┬────────────┘
│  ┌─────────────┴────────────┐
│  │       idle, prepare       │  ← 内部使用
│  └─────────────┬────────────┘
│  ┌─────────────┴────────────┐
│  │         poll              │  ← I/O 回调
│  └─────────────┬────────────┘
│  ┌─────────────┴────────────┐
│  │         check             │  ← setImmediate
│  └─────────────┬────────────┘
│  ┌─────────────┴────────────┐
│  │     close callbacks       │  ← socket.on('close')
│  └─────────────┬────────────┘
│                │
└────────────────┘

4.2 process.nextTick vs Promise

// Node.js 中 process.nextTick 优先于 Promise
setImmediate(() => console.log('1: setImmediate'));
setTimeout(() => console.log('2: setTimeout'), 0);
Promise.resolve().then(() => console.log('3: Promise'));
process.nextTick(() => console.log('4: nextTick'));

// 输出: 4, 3, 2, 1

4.3 setTimeout vs setImmediate

// 在非 I/O 上下文中,顺序不确定
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 可能 timeout 先,也可能 immediate 先

// 在 I/O 回调中,setImmediate 一定先
const fs = require('fs');
fs.readFile(__filename, () => {
  setTimeout(() => console.log('timeout'), 0);
  setImmediate(() => console.log('immediate'));
});
// 一定输出: immediate, timeout

5. requestAnimationFrame

5.1 执行时机

requestAnimationFrame 在每一帧渲染前执行,位于微任务之后、渲染之前:

宏任务 → 微任务(全部清空)→ requestAnimationFrame → 渲染

5.2 与 setTimeout 的区别

// setTimeout 可能丢帧
function animate() {
  moveElement();
  setTimeout(animate, 16); // 不保证与屏幕刷新同步
}

// rAF 与屏幕刷新同步
function animate() {
  moveElement();
  requestAnimationFrame(animate); // 在下一帧渲染前执行
}

6. 实践建议

6.1 任务拆分

// 长任务拆分为多个微任务
async function chunkedProcess(data) {
  const CHUNK_SIZE = 100;
  for (let i = 0; i < data.length; i += CHUNK_SIZE) {
    const chunk = data.slice(i, i + CHUNK_SIZE);
    processChunk(chunk);
    if (i + CHUNK_SIZE < data.length) {
      await new Promise((resolve) => setTimeout(resolve, 0));
    }
  }
}

6.2 优先级调度

// 高优先级任务用微任务
function urgentUpdate(data) {
  queueMicrotask(() => render(data));
}

// 低优先级任务用宏任务
function backgroundSync() {
  setTimeout(() => syncToServer(), 0);
}