javascript事件循环执行顺序为同步代码→微任务→宏任务:先执行全部同步任务并构建调用栈,同步结束立即清空微任务队列(含新加入的微任务),再执行一个宏任务,循环往复;浏览器中宏任务间隙会触发ui渲染。

要分析 JavaScript 事件循环各阶段的执行特征,关键不是背概念,而是抓住“谁在什么时候、以什么顺序、处理哪类任务”这三点。核心在于观察同步代码、微任务、宏任务三者的实际调度节奏,并结合浏览器或 Node.js 的运行环境差异来判断行为。
看同步代码执行阶段
这是整个流程的起点,也是唯一能直接修改调用栈的部分。所有顶层 script、函数调用、语句执行都发生在这里。
- 同步代码逐行执行,遇到函数就入栈,返回就出栈,形成清晰的调用轨迹
- 遇到异步 API(如 setTimeout、fetch、addEventListener)时,JS 引擎不等待结果,而是把回调登记到对应 Web API 线程(如定时器线程、网络线程),自己立刻继续往下走
- 遇到 Promise.resolve().then() 这类微任务源,回调不会立即执行,而是被推入微任务队列,等当前同步任务彻底结束再统一处理
盯紧微任务清空环节
微任务是事件循环中“插队最狠”的一类,它总在每个宏任务结束后、下一个宏任务开始前被执行,而且必须一次性全部清空。
- 典型微任务:Promise.then/catch/finally 回调、queueMicrotask、MutationObserver 回调
- 注意:微任务内部如果再产生新的微任务(比如在 then 里又写一个 Promise.then),这个新任务也会被加入当前微任务队列末尾,并在本轮清空过程中执行,不会等到下一轮
- 微任务没有“优先级”之分,只讲“先进先出”,但执行是连续无中断的——也就是说,只要微任务队列非空,主线程就不会去取宏任务
识别宏任务切换时机
宏任务决定了事件循环的大节奏,每次只取一个执行,执行完才检查微任务,再取下一个。它的切换点就是页面响应、用户交互、渲染帧插入的关键窗口。
- 常见宏任务:script 脚本本身、setTimeout/setInterval 回调、I/O 回调(如 fetch.then)、UI 事件(click、input)、requestAnimationFrame 回调
- 宏任务之间天然存在“间隙”,浏览器会利用这些间隙做渲染(paint)、布局(layout)、响应用户输入。这就是为什么长同步任务会导致卡顿——它霸占了整个宏任务时段,没给渲染留时间
- 不同宏任务来源可能来自不同线程(如定时器线程触发 setTimeout,网络线程触发 fetch 回调),但最终都由 JS 主线程按队列顺序串行执行
用真实输出反推执行路径
最直接有效的分析方式,就是写一段混合了同步、微任务、宏任务的代码,观察控制台输出顺序,再对照事件循环规则倒推每一步发生了什么。
- 例如:console.log('A') → 同步执行;Promise.resolve().then(() => console.log('C')) → 入微任务队列;setTimeout(() => console.log('B'), 0) → 入宏任务队列;console.log('D') → 同步执行。最终输出一定是 A → D → C → B
- 嵌套场景更明显:在 setTimeout 回调里再 new Promise().then(...),那个 then 就属于“下一个微任务轮次”,但它仍会在该 setTimeout 宏任务结束后立刻执行,而不是等下一个 setTimeout
- 配合 performance.now() 或 console.time() 可量化各阶段耗时,辅助判断是否存在意外的长任务阻塞
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











