事件循环
结论先行(一句话版)
浏览器是: 👉 执行 一个 macrotask 👉 然后 清空 microtasks 👉 然后 “可能” 进行一次渲染 👉 再进入下一轮循环
注意两个关键限定词:
- 是 一个 macrotask
- 渲染是 可能,不是必然
一、标准事件循环的一次“完整迭代”
你可以在脑中记成这段伪代码:
while (true) {
task = getNextMacrotask();
execute(task);
while (microtaskQueue.notEmpty()) {
execute(microtask);
}
if (shouldRender()) {
render();
}
}
你刚才问的那句话,翻译成底层就是:
“是不是一个 macrotask → 清空 microtask → 再 render?”
👉 是的,顺序就是这样
二、逐步拆解(为什么是这个顺序)
1️⃣ 为什么是「一个」macrotask?
如果不是一个,而是:
while (macrotaskQueue.notEmpty()) {
execute(macrotask);
}
那会导致:
- 定时器 / 网络 / IO 把 JS 线程占满
- 用户输入永远插不进来
- 页面卡死
👉 所以浏览器强制:一次只跑一个
2️⃣ 为什么 microtask 必须“清空”?
microtask 的设计目标是:
“在同一个逻辑连续体里执行”
典型例子:
doA();
Promise.then(doB);
doC();
开发者预期顺序:
A → C → B
如果 microtask 不清空:
- 状态可能被半更新
- 抽象直接崩溃
👉 所以规范规定:
microtask queue 要一直跑到空
3️⃣ 为什么 render 在 microtask 之后?
因为 render 依赖的是:
- 最终稳定的 DOM
- 最终稳定的 state
如果 DOM 还会被 Promise 改:
Promise.then(() => (el.style.width = "100px"));
那先 render 再改,屏幕就会抖。
👉 所以必须:
JS(含 microtask)完全跑完
↓
才有 render 的资格
三、关键细节:render 不是每一轮都有
这是很多人忽略、也是你这一步已经踩到的地方。
浏览器会问一句:
❓ 这一轮值得 render 吗?