javascript事件循环以调用栈清空为调度起点,宏任务执行后立即处理其微任务队列,再进行渲染;chrome performance面板可直观追踪task、microtask及layout/paint帧级执行顺序。

JavaScript 是单线程语言,靠事件循环(Event Loop)协调同步任务、异步回调与宏/微任务的执行顺序。理解它,关键不是背流程,而是看清“调用栈清空后,下一步从哪取任务”。
调用栈是调度的起点
所有同步代码都在调用栈中自上而下执行。只要栈没空,事件循环就不会检查任务队列。比如:
setTimeout(() => console.log('A'), 0);console.log('B');
while (Date.now() console.log('C');
这段代码会先输出 B,然后卡住 5 秒,最后输出 C;而 A 要等到整个同步脚本执行完、调用栈彻底清空后,才可能被事件循环取出执行。
宏任务与微任务分层排队
事件循环每次只从宏任务队列取一个任务执行(如 script、setTimeout、setInterval、I/O、UI 渲染),但执行完该宏任务后,会**立即、持续地清空当前所有微任务队列**(如 Promise.then、queueMicrotask、MutationObserver 回调)。
- 微任务总在当前宏任务结束、下一个宏任务开始前执行完毕
- 多个 Promise 链式调用产生的 then,会全部加入本轮微任务队列,依次执行
- 在微任务中再注册新的微任务(如 then 里再写 then),也会被加入**同一轮**微任务队列末尾,继续执行
渲染时机受事件循环约束
浏览器通常把 UI 渲染(重排、重绘)当作一个特殊的宏任务,安排在当前宏任务 + 所有微任务执行完之后、下一个宏任务开始之前。这意味着:
- 连续修改 DOM 不会逐次渲染,而是等 JS 执行完再批量更新
- 想强制触发一次渲染,可用 setTimeout(() => {}, 0) 或 queueMicrotask(() => {}) 切出当前上下文——前者让渲染有机会插入在下一个宏任务前,后者则无法触发渲染(因仍在同一宏任务周期内)
真实调试建议:用 DevTools 看执行帧
Chrome DevTools 的 **Performance 面板**可录制运行过程,展开“Main”线程,你能清晰看到:
- 每一段“Task”对应一个宏任务(含 script、timer、callback)
- 紧随其后的“Microtask”块,就是该宏任务触发的所有微任务
- 中间穿插的 “Layout”、“Paint”,就是浏览器插入的渲染任务
比纯脑补流程图更可靠,尤其遇到 setTimeout 延迟异常、Promise 执行顺序不符预期时,直接看帧级时间轴,一目了然。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











