事件循环是动态调度规则,核心是用双队列时间轴图清晰呈现同步执行、微任务清空、宏任务轮转的顺序:同步语句立即运行;.then等注册微任务,同步结束后批量执行;setTimeout等进入宏任务队列,待微任务清空后逐个执行。

事件循环本身不是一张静态图,而是一套动态调度规则。画图的关键不是还原“JavaScript引擎内部”,而是把任务入队时机和执行先后顺序清晰暴露出来——用最简结构讲清谁先谁后、为什么这么排。
用双队列时间轴图表达核心节奏
横向画一条时间线,标出关键阶段节点:同步开始 → 同步结束 → 微任务清空 → 渲染(可选)→ 下一个宏任务。上方画“微任务队列”,下方画“宏任务队列”,每条队列旁标注执行规则:
- 微任务队列右侧写:“当前宏任务结束后立即执行,全部清空才停”
- 宏任务队列右侧写:“每次只取队首一个,等微任务全跑完才轮到”
- 遇到
Promise.then就在线上对应位置画箭头指向微任务队列,标注“then 回调” - 遇到
setTimeout就画箭头指向宏任务队列,标注“setTimeout 回调”
重点标出三类代码的归属位置
不靠记忆分类,而是逐行判断并在线上打点定位:
-
同步语句(如
console.log('A')、let x = 1)——落在“同步执行段”,立刻运行,不进任何队列 -
微任务注册点(如
.then()、queueMicrotask()、async函数中await后的代码)——在同步执行过程中触发,但回调内容记入微任务队列,等待同步结束后批量执行 -
宏任务注册点(如
setTimeout、setInterval、整个 script 标签)——回调被塞进宏任务队列,要排队等前一轮所有微任务跑完
避免常见误读的图示细节
很多图容易误导人,画的时候要主动堵住理解漏洞:
-
new Promise(() => console.log('X'))中的console.log('X')是同步执行的,要在“同步段”标出,不能画进微任务队列 -
async function f() { await Promise.resolve(); console.log('Y') }中,console.log('Y')实际等价于.then(() => console.log('Y')),必须落在微任务队列里 - 如果一个
.then回调里又调了queueMicrotask,新任务追加到微任务队列尾部,图中要体现“继续清空”,不能画成跳回宏任务 - 浏览器渲染通常插在微任务清空之后、下一个宏任务之前,可在时间轴上加“Render”标记,帮助理解 UI 更新时机
不需要画引擎内部结构,只需呈现调度逻辑
不用画调用栈、堆、V8引擎模块或 Node.js 阶段;也不必模拟“事件多路分用器”。真正影响你写出正确异步代码的,只有三点:哪段是同步、谁进了微队列、谁进了宏队列,以及它们怎么串起来。图越贴近这个目标,越有用。

















