V8 本身不实现事件循环。
事件循环属于 宿主环境(Node.js 的 libuv,浏览器的 event loop)。
V8 只负责:执行 JS、管理 Call Stack、维护 Microtask Queue。
一、完整角色拆分
1️⃣ V8 引擎负责什么
解析 JS → AST → 字节码 / JIT
执行同步代码(Call Stack)
管理 Microtask Queue
Promise.thenqueueMicrotaskMutationObserver(浏览器)
❌ 不负责:
- 定时器
- I/O
- 网络
- 事件循环调度
2️⃣ 事件循环是谁实现的
| 环境 | 实现 |
|---|---|
| Node.js | libuv |
| 浏览器 | HTML Event Loop 规范 |
它们调度 宏任务(task / macrotask),并在关键时机调用 V8 去:
👉 清空 microtask queue
二、Node.js 事件循环的真实结构(libuv)
不是一句“宏任务 / 微任务”能解释清楚的。
libuv 六个阶段(顺序固定)
1 | ┌───────────────────────────┐ |
每一个阶段结束后:
👉 Node 会调用 V8,清空 Microtask Queue
三、Microtask 的地位(比你想的更高)
Microtask 的本质
- 不是事件循环阶段
- 是 V8 的“执行尾钩子”
规则只有一条:
每次 JS 执行栈清空后,必须立刻清空 microtask
Microtask 包括
Promise.then / catch / finallyqueueMicrotask- (浏览器)
MutationObserver
Node.js 额外插队机制
Node 还有一个 nextTickQueue:
优先级(Node):
1 | process.nextTick |
process.nextTick 是反人类设计,会饿死 I/O,不要滥用。
四、执行顺序的硬规则(必须背)
1️⃣ 同步永远第一
1 | console.log(1); |
输出:
1 | 1 |
解释:
- 同步 → microtask → 下一轮宏任务
2️⃣ microtask 会“清空到死”
1 | Promise.resolve().then(() => { |
输出:
1 | 1 |
microtask 内部再产生 microtask,会继续执行,直到队列为空。
3️⃣ setImmediate vs setTimeout(Node)
1 | setTimeout(() => console.log('timeout')); |
❌ 没有绝对顺序
✔️ 取决于是否在 I/O 阶段触发
在 I/O callback 里:
1 | fs.readFile('a.txt', () => { |
必然:
1 | immediate |
五、浏览器事件循环(与 Node 的关键差异)
浏览器宏任务来源
scriptsetTimeoutsetIntervalpostMessageMessageChannelsetImmediate❌(非标准)
浏览器规则(简化版)
1 | 取一个 task(宏任务) |
Node 没有渲染阶段。
六、一个常见致命误区
❌ 误区:
“Promise 是异步的,所以比 setTimeout 慢”
✔️ 真相:
Promise 的回调是 当前宏任务结束后立即执行
它比任何定时器都快
七、工程级理解
什么时候用 microtask
- 状态收敛
- 批量更新
- 保证当前逻辑后立即执行,但不打断同步栈
什么时候用宏任务
- 拆大任务(防止阻塞)
- UI 让步
- I/O 等待
以下为bad case
- 用
process.nextTick写控制流 - 用 Promise 模拟定时器
- 依赖 setTimeout(0) 精准调度