事件循环执行时机分三步:先执行一个宏任务,再立即清空微任务队列(含新加入的),最后浏览器可能渲染并取下一个宏任务。

直接抓住“执行时机”这个锚点,就能把大多数事件循环题讲清楚。不用背顺序,重点说清当前代码何时结束、微任务何时检查、下一个宏任务何时开始。
先分清“现在执行”和“安排以后执行”
同步代码(如 console.log、new Promise 执行器)是立刻运行的;而 setTimeout 是向浏览器登记一个将来回调,Promise.then 是把回调放进微任务队列——它们都不插进当前调用栈。
- new Promise(fn):fn 立即执行,但 then/catch 回调不执行,只排队
- async 函数:await 前的代码同步执行,await 后的代码被包装成微任务
- setTimeout(fn, 0):不是“马上”,而是“尽快安排进宏任务队列”,要等当前宏任务+所有微任务走完
记住三步固定节奏
每轮事件循环只做三件事,且顺序不可变:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
- 执行一个宏任务(比如整个 script、一次 setTimeout 回调)
- 立即清空微任务队列(所有已排队的 Promise.then、await 后续、queueMicrotask 都执行完,包括过程中新加入的)
- 浏览器可能渲染 UI(仅浏览器环境),然后取下一个宏任务
遇到嵌套或递归微任务要警惕
如果微任务里又调用 Promise.resolve().then 或 queueMicrotask,新任务会追加到当前微任务队列末尾,并在本轮继续执行——直到队列为空才停。
- 这可能导致界面卡顿:没有机会渲染、无法响应点击
- 常见陷阱:在 Promise.then 里反复 resolve 同一个 Promise,或用 await 模拟死循环
- 实际开发中,可用 setTimeout(..., 0) 主动切出当前轮次,给 UI 留出响应窗口
浏览器和 Node.js 不要混答
两者事件循环阶段不同,尤其 Node.js 有 timers → pending callbacks → idle/prepare → poll → check → close callbacks 六个阶段,且 process.nextTick 优先级高于所有微任务。
- 面试时若被问区别,先确认环境:“您问的是浏览器,还是 Node.js?”
- 浏览器中没有 process.nextTick 和 setImmediate;Node.js 中没有 UI 渲染阶段
- 同一段代码,在两个环境输出可能不同(比如多个 setImmediate 和 Promise.then 的顺序)










