前端里的宏任务和微任务

发布于:2025-09-04 ⋅ 阅读:(16) ⋅ 点赞:(0)

在前端事件循环(Event Loop)里,“宏任务”和“微任务”是两条完全不同的队列。
一句话先记住:

一次事件循环只取一个宏任务 → 执行完该宏任务 → 把本次循环产生的所有微任务全部清空 → 浏览器视情况渲染 → 进入下一轮循环。


1. 到底谁算宏任务,谁算微任务?

分类 典型 API / 场景 入队队列
宏任务 (macrotask) script 整体代码、setTimeout、setInterval、setImmediate(Node)、I/O、UI rendering、postMessage、MessageChannel、requestAnimationFrame、async 函数体本身(注意不是 await 后面的部分) 宏任务队列
微任务 (microtask) Promise.then/catch/finally、process.nextTick(Node)、queueMicrotask、MutationObserver 微任务队列

2. 事件循环的“一帧”长什么样(浏览器端)

  1. 宏任务队列里取出最老的一个任务执行(例如一个 setTimeout 回调)。
  2. 这一步产生的所有微任务按顺序全部执行完(可嵌套产生,继续清空)。
  3. 浏览器判断是否需要重排/重绘;执行 requestAnimationFrame 回调。
  4. 如果页面不需要渲染,直接进入下一轮;否则等 GPU 合成完再进入下一轮。

也就是说:“一个宏任务”对应“一整包微任务”


3. 代码走读例题

console.log(1);                // 1. 同步代码,当前宏任务

setTimeout(() => {             // 2. 新宏任务
  console.log(2);
  Promise.resolve().then(() => console.log(3));
}, 0);

Promise.resolve()              // 3. 当前宏任务里产生微任务
  .then(() => console.log(4))
  .then(() => console.log(5));

console.log(6);                // 1. 仍在当前宏任务

输出顺序:
1 → 6 → 4 → 5 → 2 → 3

讲解:

  • script 整体是第一个宏任务,先走完同步部分(1、6)。
  • 同步里遇到 Promise.then,把 4、5 依次塞进微任务队列;当前宏任务末尾立刻清空微任务,所以 4、5 紧跟着打印。
  • setTimeout 的回调是新的宏任务,要排到下一圈;它执行时又产生一个微任务 3,于是 3 在 2 之后立即打印。

4. 常见“坑”

  1. async/await 只是 Promise 的语法糖

    async function f() {
      console.log('A');
      await 1;           // 此处把后面所有代码包进 Promise.resolve().then(...)
      console.log('B');
    }
    f();
    console.log('C');
    // 结果:A → C → B
    

    await 1console.log('B') 进入微任务,所以 C 先跑。

  2. process.nextTick 比 Promise.then 还快(Node 环境专属)
    Node 里微任务分两个优先级:nextTickQueue > PromiseQueue。
    浏览器端没有 nextTick,统一当微任务即可。

  3. “零延迟”setTimeout 不一定比 Promise 快
    它只是“最小 4 ms”延迟,仍然属于下一轮宏任务,而 Promise 是本轮微任务


5. 记忆口诀

“宏一次,微清场;先同后微,再宏下一轮。”


6. 一句话总结

  • 宏任务 = “一大包”工作,浏览器每帧只做一个。
  • 微任务 = “插缝”工作,当前宏任务做完后一口气全清掉
  • 写代码时只要记住:Promise.then 永远比同级的 setTimeout 先跑

网站公告

今日签到

点亮在社区的每一天
去签到