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操作、定时器)就阻塞,会导致页面卡死。事件循环通过"任务队列+循环调度",实现非阻塞异步执行,这是其核心设计目的。
事件循环的执行流程
不是简单的"同步一微任务一宏任务",而是分执行栈和任务队列两层:
- 执行同步代码,压入执行栈执行,执行完弹出
- 遇到异步任务不立即执行,而是将其对应的回调函数放入对应的任务队列
- 执行栈为空时,先执行所有的微任务,执行完从宏任务队列取一个宏任务执行
- 宏任务执行完,再执行所有微任务,循环往复。
宏任务 & 微任务对照表
| 类型 | 具体包含的任务 | 执行优先级 | 核心特点 |
|---|---|---|---|
| 宏任务(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
执行流程
同步代码从上到下执行
- Promise.resolve() 同步执行,把 () => console.log(1) 推入微任务队列;
- 执行 console.log(2),输出:2;
- setTimeout 同步执行,把回调推入宏任务队列。
- 同步代码全部执行完毕,调用栈清空,一次性执行所有微任务
- 取出微任务回调,执行 console.log(1),输出:1。
- 微任务全部清空,进入下一轮事件循环,执行1 个宏任务
- 执行定时器回调,输出:3。
浏览器 vs Node.js 事件循环
- 微任务优先级不同
浏览器 :微任务只有 Promise、MutationObserver 等,优先级统一。
Node.js :微任务分为微任务队列 1(process.nextTick)和微任务队列 2(Promise.then 等)
process.nextTick 优先级高于 Promise,
执行顺序:nextTick → Promise → 宏任务。 - 宏任务执行逻辑不同
浏览器:每次执行一个宏任务,然后执行所有微任务,再渲染 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 解决回调地狱" | 结合事件循环原理,给出具体的性能优化与坑点规避方案 |