从 V8 引擎视角理解微任务与宏任务

一、V8 引擎的基本架构

V8 是 Google 开发的开源 JavaScript 引擎,用于 Chrome 和 Node.js 中。

vbnet 复制代码
┌─────────────────────────────────────────────────────┐
│                    V8 引擎                           │
│  ┌──────────────┐     ┌─────────────────────────┐  │
│  │  调用栈        │     │  微任务队列(V8 原生维护)  │  │
│  │  Call Stack  │     │    MicrotaskQueue       │  │
│  └──────────────┘     └─────────────────────────┘  │
└─────────────────────────────────────────────────────┘
         │ 协作
         ▼
┌─────────────────────────────────────────────────────┐
│              宿主环境(浏览器 / Node.js)               │
│  ┌──────────────────────────────────────────────┐   │
│  │       宏任务队列 + 事件循环 Event Loop          │   │
│  └──────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

关键点 :微任务队列由 V8 原生维护 ,宏任务队列和事件循环由宿主环境实现(浏览器的 Chromium / Node.js 的 libuv)。


二、微任务队列:V8 源码视角

2.1 MicrotaskQueue 的数据结构

V8 源码 src/execution/microtask-queue.h 中定义了微任务队列:

cpp 复制代码
// src/execution/microtask-queue.h(精简)
class MicrotaskQueue {
 public:
  void EnqueueMicrotask(Microtask microtask);  // 入队
  int  RunMicrotasks(Isolate* isolate);        // 清空执行

 private:
  intptr_t* ring_buffer_;  // 底层:环形缓冲区(入队/出队 O(1))
  intptr_t  capacity_;     // 容量
  intptr_t  size_;         // 当前任务数
  intptr_t  start_;        // 队头指针
};

关键设计 :底层用环形缓冲区而非普通数组,入队/出队都是 O(1),避免频繁内存分配。


2.2 RunMicrotasks:微任务是怎么执行的

src/execution/microtask-queue.cc 中的核心执行逻辑:

cpp 复制代码
// src/execution/microtask-queue.cc(精简关键逻辑)
int MicrotaskQueue::RunMicrotasks(Isolate* isolate) {
  while (size_ > 0) {           // ← 循环直到队列彻底为空
    Microtask task = GetMicrotask(isolate, start_);
    start_++;
    size_--;
    MicrotaskV8Task(isolate, task).Call(isolate);
    // 执行过程中新入队的微任务会使 size_ 变大,循环继续
  }
  return 0;
}

这段代码直接解释了一个重要行为:微任务执行期间产生的新微任务,会在本轮一并清空 ------因为 while (size_ > 0) 会持续检测队列是否为空。


2.3 微任务的触发时机:Checkpoint

V8 通过 MicrotasksPolicy 枚举控制微任务何时被触发:

cpp 复制代码
// src/execution/isolate.h(精简)
enum class MicrotasksPolicy {
  kExplicit,  // 宿主显式调用(Node.js 早期方式)
  kScoped,    // 作用域结束时执行
  kAuto       // 默认:调用栈清空时自动执行 ← 浏览器和现代 Node.js
};

V8 暴露 PerformMicrotaskCheckpoint() 接口给宿主环境:宿主每执行完一个宏任务,就调用它通知 V8 去清空微任务队列。这是宏任务和微任务协作的关键接口


三、Promise 与微任务的关联

Promise 的 .then() 为什么是微任务?答案在 V8 的 Promise 实现里。

cpp 复制代码
// src/builtins/promise-then.tq(Torque,V8 内置函数描述语言,精简)
// Promise resolve 时调用此函数
transitioning builtin FulfillPromise(promise, value) {
  const reactions = promise.reactions_or_result;

  // ★ 将 .then() 的回调包装成 PromiseReactionTask,入队微任务
  MicrotaskQueueEnqueueMicrotask(
    context,
    PromiseReactionTask { reaction: reactions, argument: value }
  );
}

白话解释Promise.resolve().then(fn) 并不立刻执行 fn,而是将 fn 封装成 PromiseReactionTask,调用 EnqueueMicrotask 放入 V8 的微任务队列,等调用栈清空后再由 RunMicrotasks 执行。


四、宏任务:宿主环境的调度

宏任务不在 V8 内部,由宿主环境维护。以 Node.js 为例,libuv 驱动宏任务,执行完毕后通知 V8:

c 复制代码
// Node.js 核心调度(大幅精简示意)
do {
  uv_run(event_loop, UV_RUN_DEFAULT);     // 1. 执行一个宏任务(libuv)
  isolate->PerformMicrotaskCheckpoint();  // 2. 通知 V8 清空微任务 ← 关键接口
} while (more_tasks);

两套队列分属不同系统,由 PerformMicrotaskCheckpoint() 这一接口联结,完成协作。


五、完整事件循环流程

scss 复制代码
[宿主环境 libuv/Chromium]              [V8 引擎]
         │                                 │
         │  取出一个宏任务 → 交给 V8        │
         │ ──────────────────────────────► │ 执行同步代码,调用栈清空
         │                                 │
         │  PerformMicrotaskCheckpoint()   │
         │ ◄────────────────────────────── │ RunMicrotasks() while(size_>0)
         │                                 │ 新产生的微任务也在此轮清空
         │                                 │
         │  (可选) UI 渲染                  │
         │  取下一个宏任务...               │

六、async/await 在 V8 中的实现

async/await 是语法糖,V8 编译时将其转换为 Promise 状态机。

javascript 复制代码
// 你写的代码
async function foo() {
  console.log('A');
  await bar();
  console.log('C');  // await 后的代码
}

V8 内部概念等价:

javascript 复制代码
function foo() {
  console.log('A');
  return bar().then(() => {
    console.log('C');  // 被包装为 PromiseReactionTask → 微任务
  });
}

V8 用 ResumeGenerator 内置函数处理 await 的恢复:await 暂停时将后续逻辑注册为 Promise 回调(EnqueueMicrotask),恢复时从微任务队列取出执行。

结论await 的暂停和恢复,本质是两次微任务队列的入队出队


七、经典代码示例解析

javascript 复制代码
console.log('1');

setTimeout(() => console.log('2'), 0);   // 宿主宏任务队列

Promise.resolve()
  .then(() => console.log('3'))          // V8 EnqueueMicrotask → cb3
  .then(() => console.log('4'));         // cb3 完成后 EnqueueMicrotask → cb4

console.log('5');

// 输出:1 → 5 → 3 → 4 → 2
步骤 调用栈 V8 微任务队列 宿主宏任务队列 输出
1 log('1') - - 1
2 setTimeout - cb2 -
3 Promise.then [cb3] cb2 -
4 log('5') [cb3] cb2 5
5 栈空 → RunMicrotasks → cb3 执行 cb4 入队 cb2 3
6 RunMicrotasks 继续 → cb4 执行 [] cb2 4
7 size_=0 退出 → 宿主取 cb2 - - 2

八、Node.js 特殊性:process.nextTick

process.nextTick 不走 V8 的微任务队列,而是 Node.js 在调用 PerformMicrotaskCheckpoint 之前,先清空自己维护的 nextTick 队列:

js 复制代码
// Node.js 内部调度顺序(lib/internal/process/task_queues.js,精简)
function processTicksAndRejections() {
  drainNextTicks();  // 1. 先清空 nextTick 队列(Node.js 自己维护)
  runMicrotasks();   // 2. 再触发 V8 清空 Promise 微任务
}
javascript 复制代码
process.nextTick(() => console.log('nextTick'));      // Node.js 独立队列
Promise.resolve().then(() => console.log('Promise')); // V8 微任务队列
// 输出:nextTick → Promise

九、总结

维度 宏任务 微任务
维护者 宿主环境(libuv / Chromium) V8 引擎(MicrotaskQueue
每轮执行数量 取出一个 全部清空while size_ > 0
触发方式 宿主事件循环调度 PerformMicrotaskCheckpoint()
Promise 关联 FulfillPromiseEnqueueMicrotask
插队行为 不能 新增微任务在本轮立即执行

核心口诀 :同步代码 → V8 清空微任务(while size_>0)→ 宿主取下一个宏任务 → V8 清空微任务 → ...


参考源码:microtask-queue.cc · promise-then.tq · Node.js task_queues.js

相关推荐
destinying2 小时前
性能优化之实战指南:让你的 Vue 应⽤跑得飞起
前端·javascript·vue.js
徐小夕3 小时前
JitWord Office预览引擎:如何用Vue3+Node.js打造丝滑的PDF/Excel/PPT嵌入方案
前端·vue.js·github
晴殇i3 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
孟陬3 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
BER_c3 小时前
前端权限校验最佳实践:一个健壮的柯里化工具函数
前端·javascript
兆子龙4 小时前
别再用 useState / data 管 Tabs 的 activeKey 了:和 URL 绑定才香
前端·架构
sudo_jin4 小时前
前端包管理器演进史:为什么 npm 之后,Yarn 和 pnpm 成了新宠?
前端·npm
叁两4 小时前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
golang学习记5 小时前
GitLens 十大神技:彻底改变你在 VS Code 中的 Git 工作流
前端·后端·visual studio code