JavaScript|第24章:事件循环与并发模型
事件循环与并发模型
- JavaScript|第24章:事件循环与并发模型
-
- 引言
- [24.1 JavaScript 的单线程模型](#24.1 JavaScript 的单线程模型)
-
- [24.1.1 为什么是单线程](#24.1.1 为什么是单线程)
- [24.1.2 单线程如何实现"并发"](#24.1.2 单线程如何实现"并发")
- [24.1.3 浏览器中的多线程](#24.1.3 浏览器中的多线程)
- [24.2 调用栈与执行上下文](#24.2 调用栈与执行上下文)
-
- [24.2.1 调用栈(Call Stack)](#24.2.1 调用栈(Call Stack))
- [24.2.2 栈溢出](#24.2.2 栈溢出)
- [24.2.3 执行上下文(Execution Context)](#24.2.3 执行上下文(Execution Context))
- [24.3 事件循环机制](#24.3 事件循环机制)
-
- [24.3.1 事件循环的基本流程](#24.3.1 事件循环的基本流程)
- [24.3.2 宏任务与微任务](#24.3.2 宏任务与微任务)
- [24.3.3 完整的事件循环流程](#24.3.3 完整的事件循环流程)
- [24.3.4 经典执行顺序分析](#24.3.4 经典执行顺序分析)
- [24.4 微任务(Microtask)](#24.4 微任务(Microtask))
-
- [24.4.1 Promise.then / .catch / .finally](#24.4.1 Promise.then / .catch / .finally)
- [24.4.2 queueMicrotask](#24.4.2 queueMicrotask)
- [24.4.3 MutationObserver](#24.4.3 MutationObserver)
- [24.4.4 微任务的嵌套](#24.4.4 微任务的嵌套)
- [24.4.5 微任务的应用场景](#24.4.5 微任务的应用场景)
- [24.5 宏任务(Macrotask)](#24.5 宏任务(Macrotask))
-
- [24.5.1 setTimeout](#24.5.1 setTimeout)
- [24.5.2 setInterval](#24.5.2 setInterval)
- [24.5.3 I/O 操作](#24.5.3 I/O 操作)
- [24.5.4 UI 渲染](#24.5.4 UI 渲染)
- [24.5.5 宏任务 vs 微任务完整对比](#24.5.5 宏任务 vs 微任务完整对比)
- [24.6 requestAnimationFrame 与 requestIdleCallback](#24.6 requestAnimationFrame 与 requestIdleCallback)
-
- [24.6.1 requestAnimationFrame(rAF)](#24.6.1 requestAnimationFrame(rAF))
- [24.6.2 requestIdleCallback(rIC)](#24.6.2 requestIdleCallback(rIC))
- [24.6.3 rAF 与 rIC 的应用场景](#24.6.3 rAF 与 rIC 的应用场景)
- [24.6.4 长任务拆分(Time Slicing)](#24.6.4 长任务拆分(Time Slicing))
- [24.7 实战](#24.7 实战)
-
- [24.7.1 实战一:输出顺序分析(面试题精讲)](#24.7.1 实战一:输出顺序分析(面试题精讲))
- [24.7.2 实战二:异步调度器(限制并发数)](#24.7.2 实战二:异步调度器(限制并发数))
- [24.7.3 实战三:防抖与节流(基于事件循环)](#24.7.3 实战三:防抖与节流(基于事件循环))
- [24.7.4 实战四:任务调度器(带优先级)](#24.7.4 实战四:任务调度器(带优先级))
- [24.8 速查表](#24.8 速查表)
-
- [24.8.1 任务类型速查](#24.8.1 任务类型速查)
- [24.8.2 执行顺序速查](#24.8.2 执行顺序速查)
- [24.8.3 性能相关 API 速查](#24.8.3 性能相关 API 速查)
- 本章小结
上篇回顾 :第23章:错误处理
下篇预告 :第25章:Promise --- 异步编程的基石
引言
JavaScript 是一门单线程 语言,同一时刻只能执行一个任务。然而,浏览器和 Node.js 却能同时处理用户点击、网络请求、定时器等多个异步操作,而不会互相阻塞。这背后的核心机制就是事件循环(Event Loop)。
javascript
console.log('1: 同步代码');
setTimeout(() => {
console.log('2: 宏任务(setTimeout)');
}, 0);
Promise.resolve().then(() => {
console.log('3: 微任务(Promise.then)');
});
console.log('4: 同步代码');
// 输出顺序:
// 1: 同步代码
// 4: 同步代码
// 3: 微任务(Promise.then)
// 2: 宏任务(setTimeout)
#mermaid-svg-xiZgoTY1x0soCuiq{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-xiZgoTY1x0soCuiq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xiZgoTY1x0soCuiq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xiZgoTY1x0soCuiq .error-icon{fill:#552222;}#mermaid-svg-xiZgoTY1x0soCuiq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xiZgoTY1x0soCuiq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xiZgoTY1x0soCuiq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xiZgoTY1x0soCuiq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xiZgoTY1x0soCuiq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xiZgoTY1x0soCuiq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xiZgoTY1x0soCuiq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xiZgoTY1x0soCuiq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xiZgoTY1x0soCuiq .marker.cross{stroke:#333333;}#mermaid-svg-xiZgoTY1x0soCuiq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xiZgoTY1x0soCuiq p{margin:0;}#mermaid-svg-xiZgoTY1x0soCuiq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xiZgoTY1x0soCuiq .cluster-label text{fill:#333;}#mermaid-svg-xiZgoTY1x0soCuiq .cluster-label span{color:#333;}#mermaid-svg-xiZgoTY1x0soCuiq .cluster-label span p{background-color:transparent;}#mermaid-svg-xiZgoTY1x0soCuiq .label text,#mermaid-svg-xiZgoTY1x0soCuiq span{fill:#333;color:#333;}#mermaid-svg-xiZgoTY1x0soCuiq .node rect,#mermaid-svg-xiZgoTY1x0soCuiq .node circle,#mermaid-svg-xiZgoTY1x0soCuiq .node ellipse,#mermaid-svg-xiZgoTY1x0soCuiq .node polygon,#mermaid-svg-xiZgoTY1x0soCuiq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xiZgoTY1x0soCuiq .rough-node .label text,#mermaid-svg-xiZgoTY1x0soCuiq .node .label text,#mermaid-svg-xiZgoTY1x0soCuiq .image-shape .label,#mermaid-svg-xiZgoTY1x0soCuiq .icon-shape .label{text-anchor:middle;}#mermaid-svg-xiZgoTY1x0soCuiq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xiZgoTY1x0soCuiq .rough-node .label,#mermaid-svg-xiZgoTY1x0soCuiq .node .label,#mermaid-svg-xiZgoTY1x0soCuiq .image-shape .label,#mermaid-svg-xiZgoTY1x0soCuiq .icon-shape .label{text-align:center;}#mermaid-svg-xiZgoTY1x0soCuiq .node.clickable{cursor:pointer;}#mermaid-svg-xiZgoTY1x0soCuiq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xiZgoTY1x0soCuiq .arrowheadPath{fill:#333333;}#mermaid-svg-xiZgoTY1x0soCuiq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xiZgoTY1x0soCuiq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xiZgoTY1x0soCuiq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xiZgoTY1x0soCuiq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xiZgoTY1x0soCuiq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xiZgoTY1x0soCuiq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xiZgoTY1x0soCuiq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xiZgoTY1x0soCuiq .cluster text{fill:#333;}#mermaid-svg-xiZgoTY1x0soCuiq .cluster span{color:#333;}#mermaid-svg-xiZgoTY1x0soCuiq 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-xiZgoTY1x0soCuiq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xiZgoTY1x0soCuiq rect.text{fill:none;stroke-width:0;}#mermaid-svg-xiZgoTY1x0soCuiq .icon-shape,#mermaid-svg-xiZgoTY1x0soCuiq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xiZgoTY1x0soCuiq .icon-shape p,#mermaid-svg-xiZgoTY1x0soCuiq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xiZgoTY1x0soCuiq .icon-shape .label rect,#mermaid-svg-xiZgoTY1x0soCuiq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xiZgoTY1x0soCuiq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xiZgoTY1x0soCuiq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xiZgoTY1x0soCuiq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第24章:事件循环与并发模型
单线程模型
调用栈与执行上下文
事件循环机制
微任务
宏任务
rAF 与 rIC
实战
为什么单线程
调用栈 / 执行上下文
宏任务 vs 微任务
Promise.then / queueMicrotask
setTimeout / setInterval / I/O
requestAnimationFrame / requestIdleCallback
输出顺序分析 / 异步调度器
本章目标:
- 理解 JavaScript 的单线程模型与并发原理
- 掌握调用栈与执行上下文的概念
- 深入理解事件循环机制:宏任务与微任务的区别和执行顺序
- 了解常见微任务:
Promise.then、queueMicrotask、MutationObserver - 了解常见宏任务:
setTimeout、setInterval、I/O、UI 渲染 - 掌握
requestAnimationFrame与requestIdleCallback的使用 - 实战:输出顺序分析、异步调度器
24.1 JavaScript 的单线程模型
24.1.1 为什么是单线程
JavaScript 最初设计为浏览器脚本语言,主要用途是操作 DOM。如果是多线程,两个线程同时修改同一个 DOM 元素就会产生复杂的同步问题。因此 JavaScript 从一开始就是单线程的。
javascript
// 如果是多线程,以下代码会有问题:
// 线程 A:element.style.color = 'red';
// 线程 B:element.style.color = 'blue';
// 哪个颜色生效?需要锁机制?------ 太复杂了
// 单线程:按顺序执行,无同步问题
element.style.color = 'red';
element.style.color = 'blue'; // 最终是蓝色,行为确定
24.1.2 单线程如何实现"并发"
单线程不意味着一次只能做一件事。通过事件循环,JavaScript 可以:
- 将耗时操作(网络请求、定时器)交给宿主环境(浏览器/Node.js)处理
- 宿主环境完成后,将回调放入任务队列
- 主线程空闲时,从任务队列中取出回调执行
#mermaid-svg-4Q4KJiKvI97Vw0X2{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-4Q4KJiKvI97Vw0X2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .error-icon{fill:#552222;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .marker.cross{stroke:#333333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 p{margin:0;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .cluster-label text{fill:#333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .cluster-label span{color:#333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .cluster-label span p{background-color:transparent;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .label text,#mermaid-svg-4Q4KJiKvI97Vw0X2 span{fill:#333;color:#333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .node rect,#mermaid-svg-4Q4KJiKvI97Vw0X2 .node circle,#mermaid-svg-4Q4KJiKvI97Vw0X2 .node ellipse,#mermaid-svg-4Q4KJiKvI97Vw0X2 .node polygon,#mermaid-svg-4Q4KJiKvI97Vw0X2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .rough-node .label text,#mermaid-svg-4Q4KJiKvI97Vw0X2 .node .label text,#mermaid-svg-4Q4KJiKvI97Vw0X2 .image-shape .label,#mermaid-svg-4Q4KJiKvI97Vw0X2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .rough-node .label,#mermaid-svg-4Q4KJiKvI97Vw0X2 .node .label,#mermaid-svg-4Q4KJiKvI97Vw0X2 .image-shape .label,#mermaid-svg-4Q4KJiKvI97Vw0X2 .icon-shape .label{text-align:center;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .node.clickable{cursor:pointer;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .arrowheadPath{fill:#333333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4Q4KJiKvI97Vw0X2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4Q4KJiKvI97Vw0X2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4Q4KJiKvI97Vw0X2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .cluster text{fill:#333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .cluster span{color:#333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 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-4Q4KJiKvI97Vw0X2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4Q4KJiKvI97Vw0X2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .icon-shape,#mermaid-svg-4Q4KJiKvI97Vw0X2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .icon-shape p,#mermaid-svg-4Q4KJiKvI97Vw0X2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .icon-shape .label rect,#mermaid-svg-4Q4KJiKvI97Vw0X2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4Q4KJiKvI97Vw0X2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4Q4KJiKvI97Vw0X2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4Q4KJiKvI97Vw0X2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 委托异步操作
操作完成
主线程空闲时取出
主线程
(执行 JS 代码)
Web APIs / Node.js APIs
(非 JS 线程)
任务队列
(回调函数排队)
24.1.3 浏览器中的多线程
虽然 JavaScript 是单线程的,但浏览器本身是多线程的:
| 线程 | 职责 |
|---|---|
| JS 引擎线程 | 执行 JavaScript 代码(V8/SpiderMonkey) |
| GUI 渲染线程 | 渲染页面(与 JS 线程互斥) |
| 事件触发线程 | 处理用户交互事件 |
| 定时器线程 | 处理 setTimeout / setInterval |
| HTTP 请求线程 | 处理 XMLHttpRequest / fetch |
| Web Worker 线程 | 独立的 JS 执行线程(不与主线程共享 DOM) |
关键 :JS 引擎线程和 GUI 渲染线程是互斥的------当 JS 执行时,页面不会渲染;当页面渲染时,JS 暂停执行。这就是为什么长时间运行的 JS 会阻塞页面。
24.2 调用栈与执行上下文
24.2.1 调用栈(Call Stack)
调用栈是一种 LIFO(后进先出)的数据结构,用于跟踪函数的调用顺序。每当函数被调用,就会在栈顶添加一个栈帧(Stack Frame);函数返回时,栈帧被弹出。
javascript
function multiply(a, b) {
return a * b; // 栈帧 3
}
function square(n) {
return multiply(n, n); // 栈帧 2
}
function printSquare(n) {
const result = square(n); // 栈帧 1
console.log(result);
}
printSquare(5);
// 调用栈变化过程:
// 1. printSquare(5) 入栈
// 2. square(5) 入栈
// 3. multiply(5, 5) 入栈
// 4. multiply 返回 出栈 → 回到 square
// 5. square 返回 出栈 → 回到 printSquare
// 6. console.log(25) 入栈
// 7. console.log 返回 出栈
// 8. printSquare 返回 出栈 → 栈空
#mermaid-svg-9p9Kh0PpIL9F3onF{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-9p9Kh0PpIL9F3onF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9p9Kh0PpIL9F3onF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9p9Kh0PpIL9F3onF .error-icon{fill:#552222;}#mermaid-svg-9p9Kh0PpIL9F3onF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9p9Kh0PpIL9F3onF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9p9Kh0PpIL9F3onF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9p9Kh0PpIL9F3onF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9p9Kh0PpIL9F3onF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9p9Kh0PpIL9F3onF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9p9Kh0PpIL9F3onF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9p9Kh0PpIL9F3onF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9p9Kh0PpIL9F3onF .marker.cross{stroke:#333333;}#mermaid-svg-9p9Kh0PpIL9F3onF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9p9Kh0PpIL9F3onF p{margin:0;}#mermaid-svg-9p9Kh0PpIL9F3onF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9p9Kh0PpIL9F3onF .cluster-label text{fill:#333;}#mermaid-svg-9p9Kh0PpIL9F3onF .cluster-label span{color:#333;}#mermaid-svg-9p9Kh0PpIL9F3onF .cluster-label span p{background-color:transparent;}#mermaid-svg-9p9Kh0PpIL9F3onF .label text,#mermaid-svg-9p9Kh0PpIL9F3onF span{fill:#333;color:#333;}#mermaid-svg-9p9Kh0PpIL9F3onF .node rect,#mermaid-svg-9p9Kh0PpIL9F3onF .node circle,#mermaid-svg-9p9Kh0PpIL9F3onF .node ellipse,#mermaid-svg-9p9Kh0PpIL9F3onF .node polygon,#mermaid-svg-9p9Kh0PpIL9F3onF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9p9Kh0PpIL9F3onF .rough-node .label text,#mermaid-svg-9p9Kh0PpIL9F3onF .node .label text,#mermaid-svg-9p9Kh0PpIL9F3onF .image-shape .label,#mermaid-svg-9p9Kh0PpIL9F3onF .icon-shape .label{text-anchor:middle;}#mermaid-svg-9p9Kh0PpIL9F3onF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9p9Kh0PpIL9F3onF .rough-node .label,#mermaid-svg-9p9Kh0PpIL9F3onF .node .label,#mermaid-svg-9p9Kh0PpIL9F3onF .image-shape .label,#mermaid-svg-9p9Kh0PpIL9F3onF .icon-shape .label{text-align:center;}#mermaid-svg-9p9Kh0PpIL9F3onF .node.clickable{cursor:pointer;}#mermaid-svg-9p9Kh0PpIL9F3onF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9p9Kh0PpIL9F3onF .arrowheadPath{fill:#333333;}#mermaid-svg-9p9Kh0PpIL9F3onF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9p9Kh0PpIL9F3onF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9p9Kh0PpIL9F3onF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9p9Kh0PpIL9F3onF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9p9Kh0PpIL9F3onF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9p9Kh0PpIL9F3onF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9p9Kh0PpIL9F3onF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9p9Kh0PpIL9F3onF .cluster text{fill:#333;}#mermaid-svg-9p9Kh0PpIL9F3onF .cluster span{color:#333;}#mermaid-svg-9p9Kh0PpIL9F3onF 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-9p9Kh0PpIL9F3onF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9p9Kh0PpIL9F3onF rect.text{fill:none;stroke-width:0;}#mermaid-svg-9p9Kh0PpIL9F3onF .icon-shape,#mermaid-svg-9p9Kh0PpIL9F3onF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9p9Kh0PpIL9F3onF .icon-shape p,#mermaid-svg-9p9Kh0PpIL9F3onF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9p9Kh0PpIL9F3onF .icon-shape .label rect,#mermaid-svg-9p9Kh0PpIL9F3onF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9p9Kh0PpIL9F3onF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9p9Kh0PpIL9F3onF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9p9Kh0PpIL9F3onF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用栈(从底到顶)
全局执行上下文
printSquare(5)
square(5)
multiply(5, 5)
24.2.2 栈溢出
调用栈有大小限制,递归过深会导致栈溢出:
javascript
function infiniteLoop() {
infiniteLoop(); // 无限递归
}
// infiniteLoop();
// RangeError: Maximum call stack size exceeded
// 安全递归:有终止条件
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(10)); // 3628800
24.2.3 执行上下文(Execution Context)
每次函数调用都会创建一个执行上下文,包含:
| 组成 | 说明 |
|---|---|
| 变量环境 | 变量、函数声明、arguments 对象 |
| 作用域链 | 当前作用域 + 外层作用域的引用 |
this 绑定 |
当前上下文的 this 值 |
javascript
function outer() {
const x = 10;
function inner() {
const y = 20;
console.log(x + y); // inner 的执行上下文可以访问 outer 的 x
}
inner();
}
outer();
// 执行 inner() 时的上下文:
// {
// 变量环境: { y: 20, arguments: [] },
// 作用域链: [inner 的 VO, outer 的 VO, 全局 VO],
// this: window (默认绑定)
// }
24.3 事件循环机制
24.3.1 事件循环的基本流程
事件循环是 JavaScript 运行时的核心调度机制,它不断检查调用栈并处理任务队列:
#mermaid-svg-eubS66tKV0IyV46H{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-eubS66tKV0IyV46H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eubS66tKV0IyV46H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eubS66tKV0IyV46H .error-icon{fill:#552222;}#mermaid-svg-eubS66tKV0IyV46H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eubS66tKV0IyV46H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eubS66tKV0IyV46H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eubS66tKV0IyV46H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eubS66tKV0IyV46H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eubS66tKV0IyV46H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eubS66tKV0IyV46H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eubS66tKV0IyV46H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eubS66tKV0IyV46H .marker.cross{stroke:#333333;}#mermaid-svg-eubS66tKV0IyV46H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eubS66tKV0IyV46H p{margin:0;}#mermaid-svg-eubS66tKV0IyV46H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eubS66tKV0IyV46H .cluster-label text{fill:#333;}#mermaid-svg-eubS66tKV0IyV46H .cluster-label span{color:#333;}#mermaid-svg-eubS66tKV0IyV46H .cluster-label span p{background-color:transparent;}#mermaid-svg-eubS66tKV0IyV46H .label text,#mermaid-svg-eubS66tKV0IyV46H span{fill:#333;color:#333;}#mermaid-svg-eubS66tKV0IyV46H .node rect,#mermaid-svg-eubS66tKV0IyV46H .node circle,#mermaid-svg-eubS66tKV0IyV46H .node ellipse,#mermaid-svg-eubS66tKV0IyV46H .node polygon,#mermaid-svg-eubS66tKV0IyV46H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eubS66tKV0IyV46H .rough-node .label text,#mermaid-svg-eubS66tKV0IyV46H .node .label text,#mermaid-svg-eubS66tKV0IyV46H .image-shape .label,#mermaid-svg-eubS66tKV0IyV46H .icon-shape .label{text-anchor:middle;}#mermaid-svg-eubS66tKV0IyV46H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eubS66tKV0IyV46H .rough-node .label,#mermaid-svg-eubS66tKV0IyV46H .node .label,#mermaid-svg-eubS66tKV0IyV46H .image-shape .label,#mermaid-svg-eubS66tKV0IyV46H .icon-shape .label{text-align:center;}#mermaid-svg-eubS66tKV0IyV46H .node.clickable{cursor:pointer;}#mermaid-svg-eubS66tKV0IyV46H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eubS66tKV0IyV46H .arrowheadPath{fill:#333333;}#mermaid-svg-eubS66tKV0IyV46H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eubS66tKV0IyV46H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eubS66tKV0IyV46H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eubS66tKV0IyV46H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eubS66tKV0IyV46H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eubS66tKV0IyV46H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eubS66tKV0IyV46H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eubS66tKV0IyV46H .cluster text{fill:#333;}#mermaid-svg-eubS66tKV0IyV46H .cluster span{color:#333;}#mermaid-svg-eubS66tKV0IyV46H 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-eubS66tKV0IyV46H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eubS66tKV0IyV46H rect.text{fill:none;stroke-width:0;}#mermaid-svg-eubS66tKV0IyV46H .icon-shape,#mermaid-svg-eubS66tKV0IyV46H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eubS66tKV0IyV46H .icon-shape p,#mermaid-svg-eubS66tKV0IyV46H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eubS66tKV0IyV46H .icon-shape .label rect,#mermaid-svg-eubS66tKV0IyV46H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eubS66tKV0IyV46H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eubS66tKV0IyV46H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eubS66tKV0IyV46H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
事件循环
调用栈为空?
等待当前代码执行完毕
检查微任务队列
微任务队列有任务?
执行所有微任务
取一个宏任务执行
执行完毕后检查微任务
24.3.2 宏任务与微任务
事件循环中有两种任务队列:
| 类型 | 队列名称 | 执行时机 | 常见来源 |
|---|---|---|---|
| 宏任务 | Task Queue | 每次事件循环取一个 | setTimeout、setInterval、I/O、UI 渲染 |
| 微任务 | Microtask Queue | 每个宏任务执行完后清空 | Promise.then、queueMicrotask、MutationObserver |
关键规则:
- 同步代码最先执行
- 微任务在当前宏任务结束后、下一个宏任务开始前执行
- 微任务会全部执行完毕后,才执行下一个宏任务
- 微任务执行过程中新增的微任务,会在当前轮次继续执行
24.3.3 完整的事件循环流程
1. 从宏任务队列取一个任务执行(第一轮是脚本整体代码)
2. 执行过程中产生的微任务进入微任务队列
3. 当前宏任务执行完毕
4. 检查微任务队列,逐个执行微任务
5. 微任务执行中产生的新微任务也加入队列并继续执行
6. 微任务队列清空
7. 浏览器可能进行 UI 渲染
8. 回到步骤 1,取下一个宏任务
24.3.4 经典执行顺序分析
javascript
console.log('script start'); // 同步(当前宏任务)
setTimeout(() => {
console.log('setTimeout'); // 宏任务
}, 0);
Promise.resolve()
.then(() => {
console.log('promise1'); // 微任务
})
.then(() => {
console.log('promise2'); // 微任务(promise1 执行后产生)
});
console.log('script end'); // 同步(当前宏任务)
// 输出顺序:
// 1. script start(同步)
// 2. script end(同步)
// 3. promise1(微任务)
// 4. promise2(微任务)
// 5. setTimeout(下一个宏任务)
逐步解析:
| 步骤 | 动作 | 宏任务队列 | 微任务队列 | 输出 |
|---|---|---|---|---|
| 1 | 执行 console.log('script start') |
[setTimeout] |
[] |
script start |
| 2 | setTimeout 回调入宏任务队列 |
[setTimeout] |
[] |
|
| 3 | Promise.resolve() 创建微任务 |
[setTimeout] |
[promise1] |
|
| 4 | 执行 console.log('script end') |
[setTimeout] |
[promise1] |
script end |
| 5 | 当前宏任务结束,执行微任务 | [setTimeout] |
[] |
promise1 |
| 6 | promise1 的 then 产生新微任务 | [setTimeout] |
[promise2] |
|
| 7 | 继续执行微任务 | [setTimeout] |
[] |
promise2 |
| 8 | 微任务队列空,取下一个宏任务 | [] |
[] |
setTimeout |
24.4 微任务(Microtask)
24.4.1 Promise.then / .catch / .finally
Promise 的回调是最常见的微任务来源:
javascript
console.log('1');
Promise.resolve().then(() => {
console.log('2');
});
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// 输出:1 → 4 → 2 → 3
// 多个 Promise.then 按注册顺序执行
24.4.2 queueMicrotask
queueMicrotask 是 ES2020 新增的全局函数,专门用于将回调加入微任务队列:
javascript
console.log('1');
queueMicrotask(() => {
console.log('2: queueMicrotask');
});
Promise.resolve().then(() => {
console.log('3: Promise.then');
});
console.log('4');
// 输出:1 → 4 → 2 → 3
// queueMicrotask 和 Promise.then 在同一个微任务队列中
// 按注册顺序执行
24.4.3 MutationObserver
MutationObserver 的回调也是微任务:
javascript
// 浏览器环境
const observer = new MutationObserver(() => {
console.log('DOM 变化(微任务)');
});
observer.observe(document.body, { childList: true });
console.log('1');
document.body.appendChild(document.createElement('div'));
console.log('2');
// 输出:1 → 2 → DOM 变化(微任务)
24.4.4 微任务的嵌套
微任务中产生的新微任务会在当前轮次继续执行:
javascript
console.log('start');
Promise.resolve().then(() => {
console.log('microtask 1');
return Promise.resolve(); // 产生新的微任务
}).then(() => {
console.log('microtask 2');
});
queueMicrotask(() => {
console.log('microtask 3');
queueMicrotask(() => {
console.log('microtask 4'); // 嵌套的微任务
});
});
console.log('end');
// 输出:start → end → microtask 1 → microtask 3 → microtask 4 → microtask 2
// 注意:Promise.resolve() 作为返回值会产生额外微任务轮次
24.4.5 微任务的应用场景
javascript
// 1. 在当前任务之后、UI 渲染之前执行代码
function batchUpdate(fn) {
queueMicrotask(() => {
fn();
// 所有 DOM 变更在微任务中完成
// 浏览器只渲染一次
});
}
// 2. 确保异步操作在同步代码之后立即执行
function afterSync(callback) {
queueMicrotask(callback);
}
console.log('A');
afterSync(() => console.log('B')); // 在 A 之后、下一个宏任务之前
console.log('C');
// 输出:A → C → B
24.5 宏任务(Macrotask)
24.5.1 setTimeout
javascript
// setTimeout 将回调放入宏任务队列
console.log('1');
setTimeout(() => console.log('2'), 0); // 0ms 延迟
setTimeout(() => console.log('3'), 100); // 100ms 延迟
console.log('4');
// 输出:1 → 4 → 2 → 3(大约)
// 注意:setTimeout 的延迟是"最小延迟",不是精确延迟
setTimeout 的最小延迟:
javascript
// 浏览器中 setTimeout 有最小延迟 4ms(嵌套超过 5 层后)
const start = Date.now();
function nestedTimeout(depth) {
if (depth === 0) {
console.log(`耗时: ${Date.now() - start}ms`);
return;
}
setTimeout(() => nestedTimeout(depth - 1), 0);
}
nestedTimeout(10);
// 前几层可能 < 4ms,之后每层至少 4ms
24.5.2 setInterval
javascript
// setInterval 定期将回调放入宏任务队列
let count = 0;
const id = setInterval(() => {
count++;
console.log(`tick ${count}`);
if (count >= 5) {
clearInterval(id);
console.log('done');
}
}, 100);
// 注意:setInterval 不保证精确间隔
// 如果上一次回调执行时间过长,可能导致回调"堆积"
setTimeout 模拟 setInterval(更精确):
javascript
function preciseInterval(fn, delay) {
let timerId;
function schedule() {
timerId = setTimeout(() => {
fn();
schedule(); // 上一次执行完后再安排下一次
}, delay);
}
schedule();
return {
clear() { clearTimeout(timerId); }
};
}
// 使用
const interval = preciseInterval(() => {
console.log(new Date().toISOString());
}, 1000);
// 10 秒后停止
setTimeout(() => interval.clear(), 10000);
24.5.3 I/O 操作
javascript
// Node.js 中的 I/O 回调也是宏任务
const fs = require('fs');
console.log('1: 同步');
fs.readFile(__filename, () => {
console.log('3: I/O 回调(宏任务)');
});
Promise.resolve().then(() => {
console.log('2: 微任务');
});
// 输出:1 → 2 → 3
24.5.4 UI 渲染
浏览器的 UI 渲染也是一个宏任务,在事件循环的适当时机执行:
javascript
// 长时间运行的 JS 会阻塞渲染
const button = document.getElementById('btn');
button.addEventListener('click', () => {
button.textContent = '处理中...';
// ❌ 长时间同步操作会阻塞 UI 更新
for (let i = 0; i < 1e9; i++) { /* 耗时计算 */ }
button.textContent = '完成';
// 用户看不到"处理中...",因为渲染被阻塞了
// ✅ 使用 setTimeout 让出控制权,允许渲染
button.textContent = '处理中...';
setTimeout(() => {
for (let i = 0; i < 1e9; i++) { /* 耗时计算 */ }
button.textContent = '完成';
}, 0);
});
24.5.5 宏任务 vs 微任务完整对比
#mermaid-svg-6UpmPB9ikM8C8Aw9{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-6UpmPB9ikM8C8Aw9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .error-icon{fill:#552222;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .marker.cross{stroke:#333333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 p{margin:0;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .cluster-label text{fill:#333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .cluster-label span{color:#333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .cluster-label span p{background-color:transparent;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .label text,#mermaid-svg-6UpmPB9ikM8C8Aw9 span{fill:#333;color:#333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .node rect,#mermaid-svg-6UpmPB9ikM8C8Aw9 .node circle,#mermaid-svg-6UpmPB9ikM8C8Aw9 .node ellipse,#mermaid-svg-6UpmPB9ikM8C8Aw9 .node polygon,#mermaid-svg-6UpmPB9ikM8C8Aw9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .rough-node .label text,#mermaid-svg-6UpmPB9ikM8C8Aw9 .node .label text,#mermaid-svg-6UpmPB9ikM8C8Aw9 .image-shape .label,#mermaid-svg-6UpmPB9ikM8C8Aw9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .rough-node .label,#mermaid-svg-6UpmPB9ikM8C8Aw9 .node .label,#mermaid-svg-6UpmPB9ikM8C8Aw9 .image-shape .label,#mermaid-svg-6UpmPB9ikM8C8Aw9 .icon-shape .label{text-align:center;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .node.clickable{cursor:pointer;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .arrowheadPath{fill:#333333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6UpmPB9ikM8C8Aw9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6UpmPB9ikM8C8Aw9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6UpmPB9ikM8C8Aw9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .cluster text{fill:#333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .cluster span{color:#333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 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-6UpmPB9ikM8C8Aw9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6UpmPB9ikM8C8Aw9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .icon-shape,#mermaid-svg-6UpmPB9ikM8C8Aw9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .icon-shape p,#mermaid-svg-6UpmPB9ikM8C8Aw9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .icon-shape .label rect,#mermaid-svg-6UpmPB9ikM8C8Aw9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6UpmPB9ikM8C8Aw9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6UpmPB9ikM8C8Aw9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6UpmPB9ikM8C8Aw9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
事件循环一轮
执行一个宏任务
清空所有微任务
有 UI 更新?
执行 UI 渲染
进入下一轮
| 对比维度 | 宏任务 | 微任务 |
|---|---|---|
| 执行时机 | 事件循环每轮取一个 | 当前宏任务结束后全部执行 |
| 队列行为 | FIFO,每轮取一个 | FIFO,全部清空 |
| 新增任务 | 下一轮执行 | 当前轮次立即执行 |
| 来源 | setTimeout、setInterval、I/O、UI 渲染 | Promise.then、queueMicrotask、MutationObserver |
| 阻塞风险 | 低(每轮一个) | 高(无限微任务会阻塞渲染) |
24.6 requestAnimationFrame 与 requestIdleCallback
24.6.1 requestAnimationFrame(rAF)
requestAnimationFrame 在浏览器下一次重绘之前调用回调,通常用于动画:
javascript
// 基本用法
function animate() {
const element = document.getElementById('box');
let pos = 0;
function step() {
pos += 2;
element.style.left = pos + 'px';
if (pos < 300) {
requestAnimationFrame(step); // 下一帧继续
}
}
requestAnimationFrame(step);
}
// rAF vs setTimeout 动画对比
// ❌ setTimeout:不与显示器刷新同步,可能掉帧
function animateWithTimeout() {
setTimeout(() => {
// 16.67ms ≈ 60fps,但浏览器可能不同步
moveElement();
animateWithTimeout();
}, 16);
}
// ✅ requestAnimationFrame:与显示器刷新同步,流畅
function animateWithRAF() {
requestAnimationFrame(() => {
moveElement();
animateWithRAF();
});
}
rAF 的执行时机:
javascript
// rAF 在宏任务之后、微任务之后、渲染之前执行
console.log('1: 同步');
setTimeout(() => console.log('2: setTimeout'));
Promise.resolve().then(() => console.log('3: Promise'));
requestAnimationFrame(() => console.log('4: rAF'));
console.log('5: 同步');
// 输出:1 → 5 → 3 → 2 → 4
// rAF 在渲染前执行,通常在下一次重绘时(约 16.67ms@60fps)
24.6.2 requestIdleCallback(rIC)
requestIdleCallback 在浏览器空闲时执行回调,适合不紧急的后台任务:
javascript
// 基本用法
requestIdleCallback((deadline) => {
console.log(`空闲时间: ${deadline.timeRemaining()}ms`);
console.log(`是否超时: ${deadline.didTimeout}`);
// 在空闲时间内执行任务
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
const task = tasks.shift();
task(); // 执行一个子任务
}
// 如果还有任务,继续安排
if (tasks.length > 0) {
requestIdleCallback(processTasks);
}
});
// 带超时
requestIdleCallback((deadline) => {
// 如果 2 秒内没有空闲,强制执行
console.log(`timeRemaining: ${deadline.timeRemaining()}ms`);
console.log(`didTimeout: ${deadline.didTimeout}`);
}, { timeout: 2000 });
24.6.3 rAF 与 rIC 的应用场景
| API | 触发时机 | 帧率 | 适用场景 |
|---|---|---|---|
setTimeout(fn, 0) |
下一轮事件循环 | 不确定 | 延迟执行、让出控制权 |
requestAnimationFrame |
下一次重绘前 | ~60fps | 动画、视觉变化 |
requestIdleCallback |
浏览器空闲时 | 不确定 | 后台计算、数据预加载 |
24.6.4 长任务拆分(Time Slicing)
使用 requestIdleCallback 或 setTimeout 将长任务拆分为小片段,避免阻塞主线程:
javascript
/**
* 时间切片:将大量工作拆分为小片段执行
* @param {Function} work - 每次执行的工作函数,返回 true 表示还有更多工作
* @param {number} chunkTime - 每个片段的最大执行时间(ms)
*/
function timeSlice(work, chunkTime = 16) {
function run() {
const start = performance.now();
while (performance.now() - start < chunkTime) {
const hasMore = work();
if (!hasMore) return; // 工作完成
}
// 还有更多工作,安排下一个片段
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(run);
} else {
setTimeout(run, 0);
}
}
run();
}
// 使用:处理 100 万条数据
const data = new Array(1000000).fill(0).map((_, i) => i);
let processedCount = 0;
const batchSize = 10000;
timeSlice(() => {
const end = Math.min(processedCount + batchSize, data.length);
for (let i = processedCount; i < end; i++) {
data[i] = data[i] * 2; // 处理每条数据
}
processedCount = end;
console.log(`已处理: ${processedCount}/${data.length}`);
return processedCount < data.length; // 是否还有更多工作
}, 16);
24.7 实战
24.7.1 实战一:输出顺序分析(面试题精讲)
题目 1:基本顺序
javascript
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// 答案:A → D → C → B
// 解析:同步先执行 → 微任务 → 宏任务
题目 2:嵌套 Promise
javascript
Promise.resolve()
.then(() => {
console.log(1);
return Promise.resolve();
})
.then(() => {
console.log(2);
});
Promise.resolve()
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
// 答案:1 → 3 → 2 → 4
// 解析:两个 Promise 链交替执行微任务
// 第一轮微任务:1, 3
// 第二轮微任务:2, 4(因为 return Promise.resolve() 产生额外微任务)
题目 3:setTimeout + Promise + async/await
javascript
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 答案:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
逐步解析:
| 步骤 | 执行内容 | 微任务队列 | 宏任务队列 | 输出 |
|---|---|---|---|---|
| 1 | console.log('script start') |
[] |
[setTimeout] |
script start |
| 2 | 调用 async1() |
[] |
[setTimeout] |
|
| 3 | console.log('async1 start') |
[] |
[setTimeout] |
async1 start |
| 4 | 调用 async2() |
[] |
[setTimeout] |
|
| 5 | console.log('async2') |
[] |
[setTimeout] |
async2 |
| 6 | await 注册微任务 |
[async1 end] |
[setTimeout] |
|
| 7 | new Promise 执行器同步执行 |
[async1 end] |
[setTimeout] |
|
| 8 | console.log('promise1') |
[async1 end] |
[setTimeout] |
promise1 |
| 9 | .then 注册微任务 |
[async1 end, promise2] |
[setTimeout] |
|
| 10 | console.log('script end') |
[async1 end, promise2] |
[setTimeout] |
script end |
| 11 | 执行微任务 async1 end |
[promise2] |
[setTimeout] |
async1 end |
| 12 | 执行微任务 promise2 |
[] |
[setTimeout] |
promise2 |
| 13 | 执行宏任务 setTimeout |
[] |
[] |
setTimeout |
题目 4:setTimeout 嵌套
javascript
setTimeout(() => {
console.log(1);
Promise.resolve().then(() => console.log(2));
}, 0);
setTimeout(() => {
console.log(3);
Promise.resolve().then(() => console.log(4));
}, 0);
// 答案:1 → 2 → 3 → 4
// 解析:
// 宏任务1:输出 1,产生微任务 2
// 微任务执行:输出 2
// 宏任务2:输出 3,产生微任务 4
// 微任务执行:输出 4
题目 5:async/await 与 Promise 交织
javascript
async function foo() {
console.log('foo start');
await bar();
console.log('foo end');
}
async function bar() {
console.log('bar');
await Promise.resolve();
console.log('bar after await');
}
foo();
console.log('script end');
// 答案:
// foo start
// bar
// script end
// bar after await
// foo end
24.7.2 实战二:异步调度器(限制并发数)
javascript
/**
* 异步任务调度器
* 控制同时执行的异步任务数量
*/
class AsyncScheduler {
#maxConcurrent;
#running = 0;
#queue = [];
constructor(maxConcurrent = 3) {
this.#maxConcurrent = maxConcurrent;
}
/**
* 添加任务
* @param {Function} task - 返回 Promise 的函数
* @returns {Promise}
*/
add(task) {
return new Promise((resolve, reject) => {
this.#queue.push({ task, resolve, reject });
this.#run();
});
}
#run() {
while (this.#running < this.#maxConcurrent && this.#queue.length > 0) {
const { task, resolve, reject } = this.#queue.shift();
this.#running++;
task()
.then(resolve)
.catch(reject)
.finally(() => {
this.#running--;
this.#run(); // 任务完成,尝试启动下一个
});
}
}
get runningCount() {
return this.#running;
}
get pendingCount() {
return this.#queue.length;
}
}
// 使用:限制同时最多 2 个请求
const scheduler = new AsyncScheduler(2);
function createTask(name, duration) {
return () => new Promise((resolve) => {
console.log(`[${new Date().toISOString()}] 开始: ${name}`);
setTimeout(() => {
console.log(`[${new Date().toISOString()}] 完成: ${name}`);
resolve(`${name} done`);
}, duration);
});
}
// 添加 5 个任务,但同一时刻最多运行 2 个
scheduler.add(createTask('任务A', 2000)).then(r => console.log('结果:', r));
scheduler.add(createTask('任务B', 1000)).then(r => console.log('结果:', r));
scheduler.add(createTask('任务C', 3000)).then(r => console.log('结果:', r));
scheduler.add(createTask('任务D', 1500)).then(r => console.log('结果:', r));
scheduler.add(createTask('任务E', 500)).then(r => console.log('结果:', r));
// 时间线:
// t=0ms: 开始任务A, 开始任务B(并发 2)
// t=1000ms: 任务B完成,开始任务C(并发 2)
// t=2000ms: 任务A完成,开始任务D(并发 2)
// t=2500ms: 任务C还在跑...
// t=3500ms: 任务D完成,开始任务E(并发 2)
// t=4000ms: 任务C完成
// t=4000ms: 任务E完成
24.7.3 实战三:防抖与节流(基于事件循环)
javascript
/**
* 防抖(Debounce)
* 事件停止触发后等待 delay 时间才执行
*/
function debounce(fn, delay) {
let timerId = null;
return function(...args) {
if (timerId !== null) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn.apply(this, args);
timerId = null;
}, delay);
};
}
/**
* 节流(Throttle)
* 每隔 interval 时间最多执行一次
*/
function throttle(fn, interval) {
let lastTime = 0;
let timerId = null;
return function(...args) {
const now = Date.now();
const remaining = interval - (now - lastTime);
if (remaining <= 0) {
// 可以立即执行
if (timerId !== null) {
clearTimeout(timerId);
timerId = null;
}
lastTime = now;
fn.apply(this, args);
} else if (timerId === null) {
// 安排延迟执行(保证最后一次也能执行)
timerId = setTimeout(() => {
lastTime = Date.now();
timerId = null;
fn.apply(this, args);
}, remaining);
}
};
}
// 防抖:搜索输入框
// const searchInput = document.getElementById('search');
// searchInput.addEventListener('input', debounce((e) => {
// console.log('搜索:', e.target.value);
// fetchSearchResults(e.target.value);
// }, 300));
// 节流:窗口滚动
// window.addEventListener('scroll', throttle(() => {
// console.log('滚动位置:', window.scrollY);
// }, 100));
24.7.4 实战四:任务调度器(带优先级)
javascript
/**
* 带优先级的任务调度器
* 高优先级任务插队执行
*/
class PriorityScheduler {
#queue = []; // 按优先级排序的队列
#maxConcurrent;
#running = 0;
constructor(maxConcurrent = 2) {
this.#maxConcurrent = maxConcurrent;
}
/**
* 添加任务
* @param {Function} task - 返回 Promise 的函数
* @param {number} priority - 优先级(数字越大越先执行)
*/
add(task, priority = 0) {
return new Promise((resolve, reject) => {
// 按优先级插入队列
const entry = { task, priority, resolve, reject };
const index = this.#queue.findIndex(e => e.priority < priority);
if (index === -1) {
this.#queue.push(entry);
} else {
this.#queue.splice(index, 0, entry);
}
this.#process();
});
}
#process() {
while (this.#running < this.#maxConcurrent && this.#queue.length > 0) {
const entry = this.#queue.shift();
this.#running++;
entry.task()
.then(entry.resolve)
.catch(entry.reject)
.finally(() => {
this.#running--;
this.#process();
});
}
}
get stats() {
return {
running: this.#running,
pending: this.#queue.length
};
}
}
// 使用
const scheduler = new PriorityScheduler(2);
// 低优先级后台任务
scheduler.add(() => fetchAnalytics(), 1);
scheduler.add(() => fetchRecommendations(), 1);
// 高优先级用户操作(插队)
scheduler.add(() => fetchUserData(), 10);
scheduler.add(() => fetchNotifications(), 5);
console.log(scheduler.stats);
// { running: 2, pending: 2 }(先执行高优先级任务)
24.8 速查表
24.8.1 任务类型速查
| 类型 | API | 队列 | 执行时机 |
|---|---|---|---|
| 宏任务 | setTimeout |
Task Queue | 下一轮事件循环 |
| 宏任务 | setInterval |
Task Queue | 周期性宏任务 |
| 宏任务 | I/O | Task Queue | I/O 完成后 |
| 宏任务 | UI 渲染 | Render Queue | 微任务之后 |
| 宏任务 | requestAnimationFrame |
Animation Frame | 下一次重绘前 |
| 宏任务 | requestIdleCallback |
Idle Callback | 浏览器空闲时 |
| 微任务 | Promise.then/catch/finally |
Microtask Queue | 当前宏任务之后 |
| 微任务 | queueMicrotask |
Microtask Queue | 当前宏任务之后 |
| 微任务 | MutationObserver |
Microtask Queue | DOM 变更后 |
24.8.2 执行顺序速查
#mermaid-svg-OVEuuH04nxsUimxC{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-OVEuuH04nxsUimxC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-OVEuuH04nxsUimxC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-OVEuuH04nxsUimxC .error-icon{fill:#552222;}#mermaid-svg-OVEuuH04nxsUimxC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OVEuuH04nxsUimxC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-OVEuuH04nxsUimxC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OVEuuH04nxsUimxC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OVEuuH04nxsUimxC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-OVEuuH04nxsUimxC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OVEuuH04nxsUimxC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OVEuuH04nxsUimxC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OVEuuH04nxsUimxC .marker.cross{stroke:#333333;}#mermaid-svg-OVEuuH04nxsUimxC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OVEuuH04nxsUimxC p{margin:0;}#mermaid-svg-OVEuuH04nxsUimxC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-OVEuuH04nxsUimxC .cluster-label text{fill:#333;}#mermaid-svg-OVEuuH04nxsUimxC .cluster-label span{color:#333;}#mermaid-svg-OVEuuH04nxsUimxC .cluster-label span p{background-color:transparent;}#mermaid-svg-OVEuuH04nxsUimxC .label text,#mermaid-svg-OVEuuH04nxsUimxC span{fill:#333;color:#333;}#mermaid-svg-OVEuuH04nxsUimxC .node rect,#mermaid-svg-OVEuuH04nxsUimxC .node circle,#mermaid-svg-OVEuuH04nxsUimxC .node ellipse,#mermaid-svg-OVEuuH04nxsUimxC .node polygon,#mermaid-svg-OVEuuH04nxsUimxC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OVEuuH04nxsUimxC .rough-node .label text,#mermaid-svg-OVEuuH04nxsUimxC .node .label text,#mermaid-svg-OVEuuH04nxsUimxC .image-shape .label,#mermaid-svg-OVEuuH04nxsUimxC .icon-shape .label{text-anchor:middle;}#mermaid-svg-OVEuuH04nxsUimxC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-OVEuuH04nxsUimxC .rough-node .label,#mermaid-svg-OVEuuH04nxsUimxC .node .label,#mermaid-svg-OVEuuH04nxsUimxC .image-shape .label,#mermaid-svg-OVEuuH04nxsUimxC .icon-shape .label{text-align:center;}#mermaid-svg-OVEuuH04nxsUimxC .node.clickable{cursor:pointer;}#mermaid-svg-OVEuuH04nxsUimxC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-OVEuuH04nxsUimxC .arrowheadPath{fill:#333333;}#mermaid-svg-OVEuuH04nxsUimxC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OVEuuH04nxsUimxC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OVEuuH04nxsUimxC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OVEuuH04nxsUimxC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-OVEuuH04nxsUimxC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OVEuuH04nxsUimxC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-OVEuuH04nxsUimxC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OVEuuH04nxsUimxC .cluster text{fill:#333;}#mermaid-svg-OVEuuH04nxsUimxC .cluster span{color:#333;}#mermaid-svg-OVEuuH04nxsUimxC 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-OVEuuH04nxsUimxC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-OVEuuH04nxsUimxC rect.text{fill:none;stroke-width:0;}#mermaid-svg-OVEuuH04nxsUimxC .icon-shape,#mermaid-svg-OVEuuH04nxsUimxC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-OVEuuH04nxsUimxC .icon-shape p,#mermaid-svg-OVEuuH04nxsUimxC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-OVEuuH04nxsUimxC .icon-shape .label rect,#mermaid-svg-OVEuuH04nxsUimxC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-OVEuuH04nxsUimxC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-OVEuuH04nxsUimxC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-OVEuuH04nxsUimxC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 同步代码
微任务队列
(全部清空)
requestAnimationFrame
UI 渲染
requestIdleCallback
下一个宏任务
(setTimeout等)
24.8.3 性能相关 API 速查
| API | 用途 | 注意事项 |
|---|---|---|
performance.now() |
高精度时间戳 | 比 Date.now() 更精确 |
requestAnimationFrame |
动画 | 与显示器刷新同步 |
requestIdleCallback |
后台任务 | 不保证执行时机 |
queueMicrotask |
延迟到微任务 | 比 setTimeout(fn, 0) 更快 |
本章小结
| 知识点 | 要点 |
|---|---|
| 单线程 | JS 引擎线程与 GUI 渲染线程互斥 |
| 并发模型 | 宿主环境用多线程处理异步,回调放入任务队列 |
| 调用栈 | LIFO,跟踪函数调用顺序,过深会栈溢出 |
| 执行上下文 | 变量环境 + 作用域链 + this 绑定 |
| 事件循环 | 不断检查栈、执行微任务、取宏任务 |
| 宏任务 | 每轮事件循环取一个:setTimeout、setInterval、I/O |
| 微任务 | 当前宏任务后全部执行:Promise.then、queueMicrotask |
| rAF | 下一次重绘前执行,适合动画 |
| rIC | 浏览器空闲时执行,适合后台任务 |
| 长任务拆分 | 避免阻塞主线程,保持 UI 响应 |
一句话总结 :JavaScript 是单线程语言,通过事件循环实现异步并发------同步代码最先执行,微任务(
Promise.then、queueMicrotask)在当前宏任务结束后全部清空,然后执行下一个宏任务(setTimeout、setInterval、I/O);requestAnimationFrame在渲染前执行适合动画,requestIdleCallback在空闲时执行适合后台任务;理解这套机制是分析异步代码执行顺序、编写高性能应用的基础。
下篇预告 :第25章:Promise --- 异步编程的基石 --- 我们将深入 Promise 的三种状态、链式调用、静态方法,并手写一个完整的 Promise 实现。
作者 :xhmico | CSDN 博客 :https://blog.csdn.net/xhmico
参考资源 :The Modern JavaScript Tutorial | MDN Web Docs | ECMAScript 规范