1

宏任务(Macro Task)

定义

  • 由浏览器或Node.js环境发起的任务,代表需要较长时间执行的代码块。
  • 执行时机:在事件循环的每一轮(一个Tick)中执行一个宏任务,之后处理所有微任务。

常见类型

  1. setTimeout / setInterval:定时器回调。
  2. I/O操作:文件读写、网络请求等。
  3. DOM事件回调:如点击事件、滚动事件。
  4. requestAnimationFrame(浏览器):动画帧回调(通常视为宏任务,但部分资料归类不同)。
  5. 整体脚本代码<script>标签内的同步代码。
  6. postMessage / MessageChannel:跨文档通信。

微任务(Micro Task)

定义

  • 需要更快执行的高优先级任务,通常与Promise相关。
  • 执行时机:在当前宏任务执行完毕后、下一个宏任务开始前,立即清空微任务队列

常见类型

  1. Promise.then / Promise.catch / Promise.finally:Promise链中的回调。
  2. async/await:底层基于Promise,await后的代码视为微任务。
  3. MutationObserver(浏览器):监听DOM变化的回调。
  4. queueMicrotask:显式添加微任务的API。
  5. process.nextTick(Node.js):优先级高于其他微任务。

宏任务 (macrotask,简称task)微任务 (microtask,简称jobs)
setTimeoutPromise(then catch finally)
setIntervalMutationObserver
requestAnimationFramequeueMicrotask
setImmediateprocess.nextTick(node)
MessageChannel
I/O,事件
script(整体代码块)

执行顺序规则

  1. 事件循环流程

    • 执行一个宏任务(如整体script脚本),同步代码优先,所有同步代码立即执行
    • 执行所有微任务(直到微任务队列为空)。
    • 可能进行UI渲染(浏览器)。
    • 进入下一轮事件循环,执行下一个宏任务。
  2. 关键特点

    • 事件循环每次只处理一个宏任务,再处理其产生的微任务队列。所以,微任务队列会在 每个宏任务执行后 立即执行
    • 微任务队列(micro task queue)优先级高于宏任务队列。
    • 微任务执行期间新添加的微任务会继续执行,直到队列清空。

浏览器环境script脚本执行顺序

  1. 执行Script整体同步宏任务代码,直到调用栈call stack清空;同时遇到异步任务源(setTimeOut,Promise等)执行,委托给宿主环境监听(setTimeOut延迟时间是否到达,Promise是否reolve),添加到对应的宏和微任务队列
  2. 执行所有微任务micro task队列任务,直到清空
  3. 渲染UI,
  4. 执行异步宏任务macro-task队列的一个任务
  5. 跳到步骤2,不停循环执行
  6. 事件循环Event Loop中,每一次循环操作称为 tick

示例对比

示例1:基础执行顺序

console.log("Script Start"); // 宏任务(整体代码)

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

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

console.log("Script End"); // 宏任务

输出顺序

Script Start → Script End → Promise → setTimeout

示例2:微任务嵌套微任务

Promise.resolve().then(() => {
  console.log("Micro 1");
  Promise.resolve().then(() => {
    console.log("Micro 2"); // 新微任务,继续执行
  });
});

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

输出顺序

Micro 1 → Micro 2 → Macro 1

示例3:宏任务中产生微任务

setTimeout(() => {
  console.log("Macro 1");
  Promise.resolve().then(() => {
    console.log("Micro 1");
  });
}, 0);

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

输出顺序

Macro 1 → Micro 1 → Macro 2

示例4:大合集

console.log("Script Start"); // 同步代码

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

Promise.resolve().then(() => {
  console.log("Promise 1"); // 微任务
  queueMicrotask(() => {
    console.log("Microtask in Promise 1"); // 微任务
  });
});

setTimeout(() => {
  console.log("setTimeout 2"); // 宏任务
  queueMicrotask(() => {
    console.log("Microtask in setTimeout 2"); // 微任务
  });
}, 0);

queueMicrotask(() => {
  console.log("Microtask 1"); // 微任务
});

console.log("Script End"); // 同步代码

执行顺序详解


  1. 同步代码执行

    Script Start → Script End
  2. 处理微任务队列

    • 微任务队列初始顺序:[Promise 1, Microtask 1]
    • 执行 Promise 1

      Promise 1

      新增微任务 Microtask in Promise 1,队列变为:[Microtask 1, Microtask in Promise 1]

    • 执行 Microtask 1

      Microtask 1
    • 执行 Microtask in Promise 1

      Microtask in Promise 1
  3. 执行宏任务队列

    • 第一个宏任务 setTimeout 1

      setTimeout 1

      新增微任务 Promise in setTimeout 1,立即处理:

      Promise in setTimeout 1
    • 第二个宏任务 setTimeout 2

      setTimeout 2

      新增微任务 Microtask in setTimeout 2,立即处理:

      Microtask in setTimeout 2

      常见误区

  4. 误区1:认为微任务会在所有宏任务之后执行。

    • 纠正:微任务在 每个宏任务执行后 立即执行。
  5. 误区2:认为嵌套的宏任务会立即执行。

    • 纠正:嵌套的宏任务会被添加到队列末尾,按顺序执行。

输出顺序

  > Script Start
  > Script End
  > Promise 1
  > Microtask 1
  > Microtask in Promise 1
  > setTimeout 1
  > Promise in setTimeout 1
  > setTimeout 2
  > Microtask in setTimeout 2

常见问题与场景

1. 为什么微任务优先于宏任务?

  • 设计目标:微任务用于处理高优先级更新(如Promise状态变更),确保在渲染前完成数据更新,避免UI不一致。

2. requestAnimationFrame是宏任务吗?

  • 归类争议:通常认为其回调在渲染前执行,但具体实现可能因浏览器而异,建议单独处理。

3. UI渲染何时进行?

  • 时机:在微任务队列清空后、下一轮宏任务前,浏览器可能选择是否渲染。


总结对比表

特性宏任务(Macro Task)微任务(Micro Task)
执行时机事件循环的每一轮中执行一个当前宏任务结束后立即全部执行
优先级
常见类型setTimeout、I/O、DOM事件Promise.thenMutationObserver
队列处理单次循环处理一个单次循环处理全部
嵌套任务影响新任务进入下一轮循环新任务继续在当前循环执行

实际应用建议

  • 优化性能:高频操作(如数据更新)优先使用微任务,减少UI卡顿。
  • 避免阻塞:长时间运行的微任务会阻塞渲染,需合理拆分任务。
  • 框架中的应用:Vue的异步更新队列、React的state批处理均依赖微任务机制。

肥皂泡
385 声望6 粉丝

码农