JavaScript 里常说的两个队列:

#JavaScript 里常说的两个队列:

#1. 宏任务队列(Macrotask Queue)

存放一轮一轮执行的“大任务”,常见:

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • I/O 回调
  • UI 事件回调
  • 部分宿主环境任务

例如:

setTimeout(() => {
  console.log("宏任务");
}, 0);

#2. 微任务队列(Microtask Queue)

存放当前任务执行完后,立即清空的“小任务”,常见:

  • Promise.then / catch / finally
  • queueMicrotask
  • MutationObserver
  • process.nextTick(Node.js,优先级还有特殊规则)

例如:

Promise.resolve().then(() => {
  console.log("微任务");
});

#执行顺序

可以记成:

宏任务
  ↓
执行同步代码
  ↓
清空微任务队列
  ↓
浏览器进行渲染等
  ↓
下一个宏任务
  ↓
清空微任务队列
  ↓
...

经典例子:

console.log("1");

setTimeout(() => {
  console.log("2");
}, 0);

Promise.resolve().then(() => {
  console.log("3");
});

console.log("4");

输出:

1
4
3
2

核心记忆:

同步代码 → 微任务全部执行 → 下一个宏任务

所以 Promise.then() 通常会比 setTimeout(..., 0) 更早执行。


可以这样理解:每执行完一个宏任务,就会检查并清空一次微任务队列。

更准确的完整循环是:

┌─────────────────────┐
│     一个宏任务       │
│                     │
│   执行同步代码       │
└──────────┬──────────┘
           ↓
    清空微任务队列
           ↓
    浏览器可能进行渲染
           ↓
┌─────────────────────┐
│     下一个宏任务      │
└──────────┬──────────┘
           ↓
    清空微任务队列
           ↓
    浏览器可能进行渲染
           ↓
┌─────────────────────┐
│     再下一个宏任务     │
└──────────┬──────────┘
           ↓
    清空微任务队列
           ↓
          ...

#举个特别直观的例子

console.log("同步1");

setTimeout(() => {
  console.log("宏任务1");

  Promise.resolve().then(() => {
    console.log("微任务1");
  });
}, 0);

setTimeout(() => {
  console.log("宏任务2");

  Promise.resolve().then(() => {
    console.log("微任务2");
  });
}, 0);

执行过程:

第一个宏任务:script
    ↓
同步1
    ↓
清空微任务
    ↓
第二个宏任务:setTimeout 1
    ↓
宏任务1
    ↓
发现微任务1
    ↓
立即执行微任务1
    ↓
第三个宏任务:setTimeout 2
    ↓
宏任务2
    ↓
发现微任务2
    ↓
立即执行微任务2

所以输出:

同步1
宏任务1
微任务1
宏任务2
微任务2

#最重要的一句话

一个宏任务
    ↓
把这个宏任务产生的所有微任务清空
    ↓
下一个宏任务
    ↓
把这个宏任务产生的所有微任务清空
    ↓
下一个宏任务

而且清空微任务时,如果微任务又产生新的微任务,会继续执行,直到微任务队列彻底为空。

例如:

Promise.resolve().then(() => {
  console.log("微任务1");

  Promise.resolve().then(() => {
    console.log("微任务2");
  });
});

结果:

微任务1
微任务2

这就是为什么可以把它记成:

宏任务之间穿插微任务;每个宏任务结束后,微任务队列清空。