《你不知道的 JavaScript 执行顺序:宏任务、微任务 和 事件循环全解析!》

739 阅读3分钟

💡 前言

很多初学者都以为 JavaScript 是“单线程+异步”,但当真正遇到 setTimeoutPromise.thenasync/await 等异步代码时,却经常被执行顺序搞晕。
这背后,其实就是事件循环机制(Event Loop)在掌控一切。


🧭 一、JavaScript 执行模型总览

JavaScript 是 单线程的,采用了 事件循环机制(Event Loop) 来实现异步处理。主要分为三部分:

  • 宏任务(Macro Task)

  • 微任务(Micro Task)

  • 执行栈(Call Stack)


🧱 二、什么是宏任务(Macro Task)

宏任务是浏览器或 Node.js 提供的异步 API,它们会在 事件循环的每一轮执行一次

📌 常见宏任务:

  • setTimeout

  • setInterval

  • setImmediate(Node.js)

  • MessageChannel(浏览器)

  • 整个 script 代码块(主任务)


🔬 三、什么是微任务(Micro Task)

微任务是在当前宏任务执行完之后、进入下一轮宏任务之前,执行的一批任务。

📌 常见微任务:

  • Promise.then, catch, finally

  • queueMicrotask

  • MutationObserver(浏览器)

  • process.nextTick(Node.js,优先级更高)


🔁 四、事件循环(Event Loop)是如何调度的?

浏览器中的事件循环流程:

一个宏任务执行开始 →
   执行同步代码 →
   执行所有微任务(清空微任务队列) →
   浏览器执行渲染 →
进入下一轮事件循环

Node.js 中的事件循环流程:

Node.js 的事件循环更复杂,有多个阶段:

timers → pending → idle → poll → checkclose callbacks
          每个阶段执行完后都会清空微任务队列

🧪 五、经典例子:你能猜出输出顺序吗?

console.log('start');

setTimeout(() => {
  console.log('macro-task: setTimeout');
}, 0);

Promise.resolve().then(() => {
  console.log('micro-task: Promise');
});

console.log('end');

✅ 输出顺序:

start
end
micro-task: Promise
macro-task: setTimeout

🔍 解释:

  1. 主线程先执行同步代码:start → end

  2. Promise.then 加入微任务队列,setTimeout 加入宏任务队列

  3. 同步代码执行完毕后,先清空微任务队列

  4. 最后进入下一轮事件循环,执行宏任务中的 setTimeout


🎯 六、微任务队列是怎么执行的?

  • 是一个队列(FIFO),按顺序执行

  • 每次宏任务执行完后,清空微任务队列

  • 新加入的微任务如果是当前轮产生的,也会立即执行

例子:

Promise.resolve().then(() => {
  console.log('A');
  Promise.resolve().then(() => {
    console.log('B');
  });
});

输出:

A
B

🆚 七、浏览器 vs Node.js 的事件循环差异

特性浏览器Node.js
宏任务结构单一阶段多阶段(timers, check 等)
微任务类型Promise, queueMicrotaskPromise, process.nextTick
微任务清空时机每个宏任务后每个阶段后
setTimeout vs setImmediatesetTimeout 总是先执行顺序不确定(取决于阶段)

🧠 八、如何利用微任务优化代码?

  • 在数据变化后通过 Promise.resolve().then(...) 代替 setTimeout(..., 0),更快执行逻辑

  • 避免在微任务中产生无限递归微任务,可能导致主线程卡死

  • 使用 queueMicrotask 更加语义化地创建微任务(适用于 polyfill 或框架封装)


✍️ 九、总结

关键词概念简述
宏任务整体任务的单元,事件循环一轮一个宏任务
微任务更细粒度的异步任务,宏任务后立即执行
事件循环控制宏任务与微任务调度的机制
浏览器清晰:宏 → 微 → 渲染
Node.js多阶段 + 特殊微任务(nextTick)

📌 最后一句话总结:

微任务决定“快”,宏任务决定“先后”!
理解事件循环,是写好异步 JavaScript 的核心。

👋 如果你也遇到类似问题,欢迎留言一起讨论。 👍 如果觉得有帮助,欢迎点赞、收藏、关注支持我~