js事件循环 event loop

JS 主线程同时要干三件事

同步业务代码

处理异步回调 (请求、定时器、点击、Promise 等)

浏览器页面渲染 (重排、重绘、更新 DOM)

异步任务分成两类队列:微任务 (高优)、宏任务(普通)
#mermaid-svg-CFNvs7iK09vjqmkw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CFNvs7iK09vjqmkw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CFNvs7iK09vjqmkw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CFNvs7iK09vjqmkw .error-icon{fill:#552222;}#mermaid-svg-CFNvs7iK09vjqmkw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CFNvs7iK09vjqmkw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CFNvs7iK09vjqmkw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CFNvs7iK09vjqmkw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CFNvs7iK09vjqmkw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CFNvs7iK09vjqmkw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CFNvs7iK09vjqmkw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CFNvs7iK09vjqmkw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CFNvs7iK09vjqmkw .marker.cross{stroke:#333333;}#mermaid-svg-CFNvs7iK09vjqmkw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CFNvs7iK09vjqmkw p{margin:0;}#mermaid-svg-CFNvs7iK09vjqmkw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CFNvs7iK09vjqmkw .cluster-label text{fill:#333;}#mermaid-svg-CFNvs7iK09vjqmkw .cluster-label span{color:#333;}#mermaid-svg-CFNvs7iK09vjqmkw .cluster-label span p{background-color:transparent;}#mermaid-svg-CFNvs7iK09vjqmkw .label text,#mermaid-svg-CFNvs7iK09vjqmkw span{fill:#333;color:#333;}#mermaid-svg-CFNvs7iK09vjqmkw .node rect,#mermaid-svg-CFNvs7iK09vjqmkw .node circle,#mermaid-svg-CFNvs7iK09vjqmkw .node ellipse,#mermaid-svg-CFNvs7iK09vjqmkw .node polygon,#mermaid-svg-CFNvs7iK09vjqmkw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CFNvs7iK09vjqmkw .rough-node .label text,#mermaid-svg-CFNvs7iK09vjqmkw .node .label text,#mermaid-svg-CFNvs7iK09vjqmkw .image-shape .label,#mermaid-svg-CFNvs7iK09vjqmkw .icon-shape .label{text-anchor:middle;}#mermaid-svg-CFNvs7iK09vjqmkw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CFNvs7iK09vjqmkw .rough-node .label,#mermaid-svg-CFNvs7iK09vjqmkw .node .label,#mermaid-svg-CFNvs7iK09vjqmkw .image-shape .label,#mermaid-svg-CFNvs7iK09vjqmkw .icon-shape .label{text-align:center;}#mermaid-svg-CFNvs7iK09vjqmkw .node.clickable{cursor:pointer;}#mermaid-svg-CFNvs7iK09vjqmkw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CFNvs7iK09vjqmkw .arrowheadPath{fill:#333333;}#mermaid-svg-CFNvs7iK09vjqmkw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CFNvs7iK09vjqmkw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CFNvs7iK09vjqmkw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CFNvs7iK09vjqmkw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CFNvs7iK09vjqmkw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CFNvs7iK09vjqmkw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CFNvs7iK09vjqmkw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CFNvs7iK09vjqmkw .cluster text{fill:#333;}#mermaid-svg-CFNvs7iK09vjqmkw .cluster span{color:#333;}#mermaid-svg-CFNvs7iK09vjqmkw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CFNvs7iK09vjqmkw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CFNvs7iK09vjqmkw rect.text{fill:none;stroke-width:0;}#mermaid-svg-CFNvs7iK09vjqmkw .icon-shape,#mermaid-svg-CFNvs7iK09vjqmkw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CFNvs7iK09vjqmkw .icon-shape p,#mermaid-svg-CFNvs7iK09vjqmkw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CFNvs7iK09vjqmkw .icon-shape .label rect,#mermaid-svg-CFNvs7iK09vjqmkw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CFNvs7iK09vjqmkw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CFNvs7iK09vjqmkw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CFNvs7iK09vjqmkw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 宏任务队列

高优先级执行完毕,执行低优先级
Script脚本子队列

内联script

高优先级
UI交互子队列

click、scroll、input DOM事件
网络IO子队列

fetch、图片、资源加载回调
消息任务子队列

MessageChannel
定时器子队列

setTimeout、setInterval

低优先级
执行栈

先进后出
微任务队列

Promise.then/async await

#mermaid-svg-dRWuYB2zpnCsn2Nr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dRWuYB2zpnCsn2Nr .error-icon{fill:#552222;}#mermaid-svg-dRWuYB2zpnCsn2Nr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dRWuYB2zpnCsn2Nr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .marker.cross{stroke:#333333;}#mermaid-svg-dRWuYB2zpnCsn2Nr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dRWuYB2zpnCsn2Nr p{margin:0;}#mermaid-svg-dRWuYB2zpnCsn2Nr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .cluster-label text{fill:#333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .cluster-label span{color:#333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .cluster-label span p{background-color:transparent;}#mermaid-svg-dRWuYB2zpnCsn2Nr .label text,#mermaid-svg-dRWuYB2zpnCsn2Nr span{fill:#333;color:#333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .node rect,#mermaid-svg-dRWuYB2zpnCsn2Nr .node circle,#mermaid-svg-dRWuYB2zpnCsn2Nr .node ellipse,#mermaid-svg-dRWuYB2zpnCsn2Nr .node polygon,#mermaid-svg-dRWuYB2zpnCsn2Nr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dRWuYB2zpnCsn2Nr .rough-node .label text,#mermaid-svg-dRWuYB2zpnCsn2Nr .node .label text,#mermaid-svg-dRWuYB2zpnCsn2Nr .image-shape .label,#mermaid-svg-dRWuYB2zpnCsn2Nr .icon-shape .label{text-anchor:middle;}#mermaid-svg-dRWuYB2zpnCsn2Nr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dRWuYB2zpnCsn2Nr .rough-node .label,#mermaid-svg-dRWuYB2zpnCsn2Nr .node .label,#mermaid-svg-dRWuYB2zpnCsn2Nr .image-shape .label,#mermaid-svg-dRWuYB2zpnCsn2Nr .icon-shape .label{text-align:center;}#mermaid-svg-dRWuYB2zpnCsn2Nr .node.clickable{cursor:pointer;}#mermaid-svg-dRWuYB2zpnCsn2Nr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .arrowheadPath{fill:#333333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dRWuYB2zpnCsn2Nr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dRWuYB2zpnCsn2Nr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dRWuYB2zpnCsn2Nr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dRWuYB2zpnCsn2Nr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dRWuYB2zpnCsn2Nr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dRWuYB2zpnCsn2Nr .cluster text{fill:#333;}#mermaid-svg-dRWuYB2zpnCsn2Nr .cluster span{color:#333;}#mermaid-svg-dRWuYB2zpnCsn2Nr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dRWuYB2zpnCsn2Nr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dRWuYB2zpnCsn2Nr rect.text{fill:none;stroke-width:0;}#mermaid-svg-dRWuYB2zpnCsn2Nr .icon-shape,#mermaid-svg-dRWuYB2zpnCsn2Nr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dRWuYB2zpnCsn2Nr .icon-shape p,#mermaid-svg-dRWuYB2zpnCsn2Nr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dRWuYB2zpnCsn2Nr .icon-shape .label rect,#mermaid-svg-dRWuYB2zpnCsn2Nr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dRWuYB2zpnCsn2Nr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dRWuYB2zpnCsn2Nr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dRWuYB2zpnCsn2Nr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 同步代码/函数
Promise.then async await
setTimeout/DOM事件/fetch
初始化:script脚本加入宏任务

react项目 会生成html <script src="./bundle.js"></script>
事件循环从宏任务队列取出 1 个宏任务
执行代码,区分代码类型
压入调用栈执行,先进后出,执行完弹出
推入微任务队列Micro
推入宏任务队列Macro尾部 先进先出


很多框架(React/Vue)的状态更新、DOM 更新底层大量依赖微任务

Vue 的 nextTick、React 批量更新,底层都是微任务

  • 多次修改 data/state,不会改一次刷一次 DOM;
  • 等当前同步逻辑代码跑完,一次性执行所有微任务,批量更新视图;既保证更新及时,又提升渲染性能。
  • 避免频繁 DOM 操作造成性能损耗。

浏览器js事件周期
#mermaid-svg-Oo2Afxuoxe2UzFK1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .error-icon{fill:#552222;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .marker.cross{stroke:#333333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 p{margin:0;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .cluster-label text{fill:#333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .cluster-label span{color:#333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .cluster-label span p{background-color:transparent;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .label text,#mermaid-svg-Oo2Afxuoxe2UzFK1 span{fill:#333;color:#333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .node rect,#mermaid-svg-Oo2Afxuoxe2UzFK1 .node circle,#mermaid-svg-Oo2Afxuoxe2UzFK1 .node ellipse,#mermaid-svg-Oo2Afxuoxe2UzFK1 .node polygon,#mermaid-svg-Oo2Afxuoxe2UzFK1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .rough-node .label text,#mermaid-svg-Oo2Afxuoxe2UzFK1 .node .label text,#mermaid-svg-Oo2Afxuoxe2UzFK1 .image-shape .label,#mermaid-svg-Oo2Afxuoxe2UzFK1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .rough-node .label,#mermaid-svg-Oo2Afxuoxe2UzFK1 .node .label,#mermaid-svg-Oo2Afxuoxe2UzFK1 .image-shape .label,#mermaid-svg-Oo2Afxuoxe2UzFK1 .icon-shape .label{text-align:center;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .node.clickable{cursor:pointer;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .arrowheadPath{fill:#333333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Oo2Afxuoxe2UzFK1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Oo2Afxuoxe2UzFK1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Oo2Afxuoxe2UzFK1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .cluster text{fill:#333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .cluster span{color:#333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Oo2Afxuoxe2UzFK1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .icon-shape,#mermaid-svg-Oo2Afxuoxe2UzFK1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .icon-shape p,#mermaid-svg-Oo2Afxuoxe2UzFK1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .icon-shape .label rect,#mermaid-svg-Oo2Afxuoxe2UzFK1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Oo2Afxuoxe2UzFK1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Oo2Afxuoxe2UzFK1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Oo2Afxuoxe2UzFK1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件循环开始
执行1个宏任务
清空所有微任务队列
浏览器页面渲染:重排/重绘/更新DOM
开启下一轮事件循环

规则设计逻辑:

一个宏任务跑完,先把所有微任务收尾做完;

再统一交给浏览器渲染 UI;

最后才取下一个宏任务。

好处

不会穿插执行零散异步任务,渲染时机固定、可控;

主线程工作分段清晰,不会长时间阻塞渲染,页面不卡顿。

有多少个待执行宏任务,就会有多少次独立渲染


浏览器全部I/O

设备输入I/O:click、scroll、input、resize、touch 等用户硬件交互事件

网络I/O:fetch、XHR、WebSocket、图片 / JS/CSS 等资源加载

定时计时I/O:setTimeout、setInterval

文件I/O:FileReader 本地文件读取

剪贴板I/O:copy、paste 剪贴板读写

媒体硬件I/O:音视频、摄像头麦克风相关异步回调

Node.js I/O

磁盘文件 I/O、终端标准 I/O、TCP/UDP 网络 Socket、子进程管道 I/O

非 I/O(微任务,不属于I/O 操作)

Promise.then/catch/finally、async/await、MutationObserver、queueMicrotask

为什么需要事件循环?

答案: js是单线程语言,同一时间只能执行一段代码。如果遇到I/O操作(网络请求、DOM操作、定时器)就阻塞,会导致页面卡死。事件循环通过"任务队列+循环调度",实现非阻塞异步执行,这是其核心设计目的。

事件循环的执行流程

不是简单的"同步一微任务一宏任务",而是分执行栈和任务队列两层:

  1. 执行同步代码,压入执行栈执行,执行完弹出
  2. 遇到异步任务不立即执行,而是将其对应的回调函数放入对应的任务队列
  3. 执行栈为空时,先执行所有的微任务,执行完从宏任务队列取一个宏任务执行
  4. 宏任务执行完,再执行所有微任务,循环往复。

宏任务 & 微任务对照表

类型 具体包含的任务 执行优先级 核心特点
宏任务(Macrotask) 浏览器:script 整体代码、setTimeout/setInterval、I/O 操作、UI 交互事件(click/scroll/input)、AJAX 请求 Node.js:setTimeout、setInterval、setImmediate、I/O 回调 微任务之后,每次取一个执行 任务队列是先进先出,但一次只处理一个宏任务
微任务(Microtask) 浏览器:Promise.then/catch/finally、MutationObserver、queueMicrotask Node.js:Promise.then/catch/finally、process.nextTick(优先级最高) 执行栈空后,全部执行完 微任务队列是先进先出,一次执行完所有微任务

为什么setTimeout(() => {}, 0)也会延迟执行

回答:即使延时为 0,也会被放入宏任务队列,必须等当前执行栈和微任务执行完,才会执行。

js 复制代码
Promise.resolve().then(() => console.log(1))
console.log(2)
setTimeout(() => {console.log(3)}, 0)

执行顺序

js 复制代码
2
1
3

执行流程

同步代码从上到下执行

  1. Promise.resolve() 同步执行,把 () => console.log(1) 推入微任务队列;
  2. 执行 console.log(2),输出:2;
  3. setTimeout 同步执行,把回调推入宏任务队列。
  4. 同步代码全部执行完毕,调用栈清空,一次性执行所有微任务
  5. 取出微任务回调,执行 console.log(1),输出:1。
  6. 微任务全部清空,进入下一轮事件循环,执行1 个宏任务
  7. 执行定时器回调,输出:3。

浏览器 vs Node.js 事件循环

  1. 微任务优先级不同
    浏览器 :微任务只有 Promise、MutationObserver 等,优先级统一。
    Node.js :微任务分为微任务队列 1(process.nextTick)和微任务队列 2(Promise.then 等)
    process.nextTick 优先级高于 Promise,
    执行顺序:nextTick → Promise → 宏任务。
  2. 宏任务执行逻辑不同
    浏览器:每次执行一个宏任务,然后执行所有微任务,再渲染 UI。
    Node.js:宏任务分阶段(timers → pending callbacks → idle/prepare → poll → check → close callbacks),每个阶段处理完对应宏任务,再执行微任务。

Vue nextTick 的实现原理

核心:利用微任务实现 DOM 更新后的回调,优先使用 Promise.then(微任务),降级为 MutationObserver,再降级为 setTimeout(宏任务)。

Promise.then(首选微任务,现代浏览器)

MutationObserver(兼容无 Promise 的旧浏览器,微任务)

setTimeout(fn, 0)(兜底宏任务)

原因:微任务执行时机更早,能更快获取更新后的 DOM。

React 调度与事件循环

React 18 采用并发更新,不长时间阻塞主线程。

依靠浏览器事件循环的微任务、宏任务,把长更新任务拆分成碎片,浏览器有空隙时执行,实现可中断、可恢复的任务调度。

React 的 Scheduler 调度器基于 requestIdleCallback(宏任务)和 MessageChannel(宏任务),利用事件循环实现任务优先级调度,避免长任务阻塞主线程。

首选:MessageChannel(宏任务)

MessageChannel 创建消息通道,port.postMessage 触发的回调属于微任务;

微任务在当前同步代码结束、浏览器渲染前执行,响应速度快;

用来执行普通低优先级分片更新。

降级兜底:requestIdleCallback(宏任务)

浏览器空闲时才执行,属于宏任务;

兼容性差、有 50ms 超时限制,仅作为备用;

最低兜底:setTimeout

浏览器不支持以上 API 时,用定时器宏任务分片。

js 复制代码
const channel = new MessageChannel();
channel.port2.onmessage = () => {
  console.log('执行回调');
};
channel.port1.postMessage('hi'); // 发送消息,触发onmessage

MessageChannel是浏览器内置通信 API,创建两个端口 port1、port2,互相发消息,调用 postMessage 后,回调会加入宏任务队列消息任务子队列

MessageChannel 浏览器原生 API,本意是用来做跨环境双向通信:

  • 主线程 ↔ WebWorker
  • 父页面 ↔ iframe
  • 不同标签页、隔离脚本之间传数据

微任务执行时会一次性全部清空,中途无法渲染页面,长渲染会阻塞主线程,不适合分片;

MessageChannel 是宏任务,一轮只执行一小段渲染,执行完主动让出主线程,浏览器可穿插页面渲染与用户交互,实现可中断并发更新;

setTimeout 嵌套层级≥5 层时,强制保底 4ms 延迟,MessageChannel 无 4ms 强制延迟,比 setTimeout 调度更快;requestIdleCallback 是低优先级备用宏任务,处理后台空闲任务。

常见坑与解决

异步回调地狱:利用 Promise、async/await 基于事件循环的异步特性,扁平化代码。

DOM 更新延迟:通过 nextTick 确保 DOM 更新完成后再操作,避免多次重排重绘。

内存泄漏:避免在事件循环中无限期保留回调引用(如未清除的定时器、未解绑的事件)。

考察维度 及格回答 优秀回答
事件循环本质 单线程非阻塞机制,分宏微任务 讲清单线程痛点、执行栈 + 任务队列的底层逻辑
宏 / 微任务 说出 setTimeout、Promise 等类型 精准列举各环境类型、执行优先级、一次执行数量
跨环境差异 知道浏览器和 Node 有区别 详细说明微任务优先级、宏任务阶段差异
框架应用 知道 Vue nextTick 讲清 nextTick 的降级策略、与事件循环的结合点
工程化解决 笼统说 "用 async/await 解决回调地狱" 结合事件循环原理,给出具体的性能优化与坑点规避方案