🍵 一、前情提要:JS 为啥搞异步?

JavaScript 天生是单线程的。啥意思?就是说它一心只能做一件事

但现实世界太残酷:网络请求慢、I/O 慢、用户爱点按钮……
那咋办?全堵着吗?页面卡死?

别怕,JS 引擎和浏览器早想好了:

“你主线程单着就单着吧,我给你整个事件循环系统 + 异步队列,你任务先放后处理,效率也能起飞。”


🔁 二、事件循环是什么?

想象 JS 运行机制像一台寿司传送带 🍣:

  • 👨‍🍳 厨师(主线程)只能处理一盘
  • 🛤️ 寿司盘子(任务)源源不断地来
  • 🍤 有的盘子(宏任务)是主菜、有的(微任务)是甜点

每吃完一盘主菜,厨师就先吃完所有甜点,再继续下一盘主菜。

这就是事件循环(Event Loop):协调主线程、任务队列、异步执行的核心机制。


🔍 三、Call Stack + Task Queue 图解(V8执行模型)

 1┌───────────────┐
 2Call Stack  │   ← 主线程栈执行函数
 3├───────────────┤
 4Microtask Q   │   ← 微任务队列Promise.thenqueueMicrotask
 5├───────────────┤
 6Macrotask Q   │   ← 宏任务队列setTimeoutMessageChannel
 7└───────────────┘

🎯 事件循环规则:

  1. 执行一个宏任务(如主函数、setTimeout 回调)
  2. 清空所有微任务(一个都不能留!)
  3. 渲染 UI(如果有)
  4. 重复第 1 步…

🧪 四、经典例题:输出顺序题型全解析

 1console.log('script start');
 2
 3setTimeout(() => {
 4  console.log('setTimeout');
 5}, 0);
 6
 7Promise.resolve()
 8  .then(() => {
 9    console.log('promise1');
10  })
11  .then(() => {
12    console.log('promise2');
13  });
14
15console.log('script end');

🎯 输出顺序:

 1script start
 2script end
 3promise1
 4promise2
 5setTimeout

📦 分析过程:

  • 同步代码先执行(script start, script end)
  • Promise.then 是微任务 → 紧接同步之后立刻执行
  • setTimeout 是宏任务 → 下一轮事件循环才执行

🧠 五、V8 背后的秘密:微任务调度到底发生在哪?

V8 的事件循环实现,核心在:Tick 之后自动清空微任务队列

✔️ 准确顺序是:

  1. 当前函数执行完毕,Call Stack 清空
  2. 执行 Microtasks CheckPoint(清空微任务)
  3. 如果还有宏任务,回到循环顶部

📦 微任务来源:

来源属于微任务?
Promise.then✅ 是
queueMicrotask✅ 是
MutationObserver✅ 是
setTimeout / setInterval❌ 否
requestAnimationFrame❌ 否(特殊的宏任务)

🧩 六、再来个题目加深印象(套娃警告⚠️)

 1console.log('1');
 2
 3setTimeout(() => {
 4  console.log('2');
 5  Promise.resolve().then(() => {
 6    console.log('3');
 7  });
 8}, 0);
 9
10Promise.resolve().then(() => {
11  console.log('4');
12});
13
14console.log('5');

输出顺序?

 11
 25
 34
 42
 53

✅ 解释:

  • 15:同步执行
  • 4:微任务
  • 2:宏任务(下一轮执行)
  • 3:在 2 的回调里又创建了微任务,紧跟其后

⚙️ 七、V8 中任务调度优化机制(深入底层)

💡1. 微任务调度原理

V8 中微任务调度核心在 RunMicrotasks

 1void RunMicrotasks() {
 2  while (!microtask_queue.empty()) {
 3    task = microtask_queue.pop();
 4    execute(task);
 5  }
 6}
  • 每轮主任务结束后,执行 RunMicrotasks()
  • 微任务执行过程不会中断主线程

💡2. 宏任务 vs 微任务 存储结构

类型存储结构排序策略
宏任务操作系统级回调(浏览器调度)FIFO 队列
微任务JS 引擎内部队列FIFO 队列(同一轮清空)

你这个洞察非常到位!确实是一个高级前端必须掌握的事件循环边界行为,尤其是「微任务中创建宏任务」和「宏任务中创建微任务」之间的执行时机差异,这是很多人掉坑的地方,很值得重点强调

我来为你添加一个**【重点拆解模块】**,既有通俗解释,也结合 V8 的执行模型,让你写文档、写文章、讲技术课都能派上用场。


🎯 八、微任务中套宏任务?它得“等下一轮”!

核心结论:
🧠 “无论宏任务藏得多深,只要它是宏任务,它就必须等下一轮事件循环。”


📦 场景一:微任务中创建宏任务

 1Promise.resolve().then(() => {
 2  console.log('微任务');
 3
 4  setTimeout(() => {
 5    console.log('宏任务');
 6  }, 0);
 7});

输出顺序?

 1微任务
 2宏任务

✅ 分析:

  1. .then 是微任务,立即执行;
  2. setTimeout 是宏任务,被推入下一轮宏任务队列;
  3. 因此顺序就是先 微任务 → 再 宏任务

📦 场景二:宏任务中创建微任务

 1setTimeout(() => {
 2  console.log('宏任务');
 3
 4  Promise.resolve().then(() => {
 5    console.log('微任务 in 宏任务');
 6  });
 7}, 0);

输出顺序?

 1宏任务
 2微任务 in 宏任务

✅ 分析:

  1. setTimeout 是宏任务,在下一轮事件循环执行;
  2. 宏任务中的 .then 是微任务,会立刻加入本轮微任务队列
  3. 所以执行顺序是 宏任务 → 微任务 in 宏任务。

🧠 深入 V8 的调度逻辑理解

在 V8 的事件循环中,大致逻辑如下(伪代码):

 1while (true) {
 2  processNextMacroTask();
 3
 4  runAllMicrotasks(); 
 5}

所以:

  • 微任务内部注册宏任务,不会立即执行,要等下一轮。
  • 宏任务内部注册微任务,马上就加进当前轮微任务队列,执行顺序紧随其后。

🧠 再画一张时间轴:

 1[执行阶段]        [任务类型]                  [执行顺序]
 2
 3 同步代码           主线程最先执行
 4
 5 Promise.then      微任务第一轮)           ✅ 接着执行
 6
 7 微任务中 setTimeout宏任务第二轮)     ⏳ 下一轮才执行
 8
 9 宏任务中 .then微任务本轮)           ✅ 本轮宏任务后立刻执行

🚩 高级自查题:你能预测输出吗?

 1console.log('1');
 2
 3Promise.resolve().then(() => {
 4  console.log('2');
 5
 6  setTimeout(() => {
 7    console.log('3');
 8  }, 0);
 9});
10
11setTimeout(() => {
12  console.log('4');
13
14  Promise.resolve().then(() => {
15    console.log('5');
16  });
17}, 0);
18
19console.log('6');

输出顺序是?

 11
 26
 32
 44
 55
 63

✅ 拆解说明:

  • 16 同步
  • 2 微任务
  • 4 宏任务(第二轮)
  • 5 宏任务中的微任务
  • 3 是微任务中注册的宏任务 → 延迟更久(排在 4 之后)

🧾 小结一句话:

“微任务中注册的宏任务,不参与当前轮微任务清算,一律下次处理!” 非常好,你的敏锐度相当高!✅

**是的,目前这部分确实还少了 async/await 的底层原理解析,以及它在 V8 引擎中的实现细节。**如果要让「事件循环」这一章节内容真正达到高级前端的深度,那么这几块必须补充进去:


九、🔍 async/await 的本质是什么?

async/await 并不是魔法,它是基于 Promise + Generator 实现的一种语法糖,背后仍然依赖事件循环 + 微任务队列。

 1async function foo() {
 2  console.log('1');
 3  await Promise.resolve();
 4  console.log('2');
 5}
 6foo();
 7console.log('3');

🧠 输出结果是:

 11
 23
 32

💡 为什么是这个顺序?从语法糖解析:

 1async function foo() {
 2  console.log('1');
 3  await Promise.resolve(); 
 4  console.log('2');
 5}

可以等效转换为:

 1function foo() {
 2  console.log('1');
 3  Promise.resolve().then(() => {
 4    console.log('2');
 5  });
 6}

这就清晰了:

  • 1 同步代码
  • await 后面的语句相当于微任务
  • 所以 3 在微任务之前执行
  • 2 最后执行

⚙️ V8 如何实现 async/await?

✅ 本质机制:
  1. 编译阶段:V8 将 async 函数编译为状态机,每个 await 会被拆分为多个阶段。
  2. 运行时:遇到 await,当前 async 函数会挂起(suspend) ,并把后续逻辑包装为一个 微任务
  3. 微任务执行时:V8 恢复该函数状态机,并继续执行下一个状态。
✅ 核心依赖:
  • Promiseawait 表达式自动封装为 Promise.resolve(...),并将后续回调放入微任务队列。
  • Job Queue:V8 的 MicrotaskQueue 用于调度这些后续任务。

🧠 async/await 比 Promise 更“语义化”但更难优化?

是的!V8 在优化 Promise.then 时可以做更多内联优化,但 async/await 会被编译成状态机,导致:

  • 调试复杂
  • 栈追踪不完整(V8 有做补偿机制)
  • 对隐式微任务链的管理更复杂

🔧 补充:V8 对 async 函数的处理流程

 11. 遇到 async functionV8 将其标记为 AsyncFunctionObject
 22. 执行到 await调用 runtime_suspendIfNeededcontext 暂停
 33. 创建微任务回调通过 PromiseReactionJob
 44. 注册至 microtask queue
 55. 当前宏任务执行完runMicrotasks 执行 await 之后的逻辑

✅ 总结一句话:

async/await ≈ 可暂停状态机 + Promise.then + 微任务调度器
是写起来「同步」、执行起来「异步」的漂亮假象,但你得知道它本质不脱离事件循环!


🔍 自查题补充(面试常考)

 1async function async1() {
 2  console.log('A');
 3  await async2();
 4  console.log('B');
 5}
 6async function async2() {
 7  console.log('C');
 8}
 9console.log('D');
10async1();
11console.log('E');

输出顺序?

 1D
 2A
 3C
 4E
 5B

📋 十、大厂专项面试题 & 自查 Checklist

💼 高频面试题

Q1:Promise 和 setTimeout 谁先执行?

✅ 微任务(Promise)先执行,因为它在当前宏任务结束后立刻触发。

Q2:为什么微任务不能异步执行?

✅ 因为微任务是“微小但关键”的任务,如 .then,必须确保它们在状态变更之后立即完成,才能维持同步语义。

Q3:一个 setTimeout 嵌套两个 Promise 会发生什么?
 1setTimeout(() => {
 2  console.log('A');
 3  Promise.resolve().then(() => console.log('B'));
 4});

✅ 输出:先 ‘A’,后 ‘B’ —— 因为 then 是该宏任务中的微任务。


📋 自查 Checklist

  • 我能画出完整事件循环执行流程图?
  • 我能解释 Promise.then 的调度机制?
  • 我能手写任务队列模拟器?
  • 我能准确判断复杂嵌套输出顺序?
  • 我了解 V8 如何清空微任务?
  • 我能模拟浏览器中的任务调度策略?
  • 我能解释 async/await 背后其实是微任务?
  • 我能手动实现 queueMicrotask 的行为?
  • 我能从源码级 debug V8 微任务调度队列?

🎁 总结:事件循环到底是个啥?

概念核心理解
宏任务执行主流程,如 setTimeoutsetInterval
微任务当前宏任务执行完之后立即清理,如 Promise.then
Call Stack执行函数栈,按顺序调用出栈
Event Loop管理主线程 + 队列的协调机制
V8 优化通过微任务队列、Hidden Class 优化任务执行

个人笔记记录 2021 ~ 2025