JavaScript|第24章:事件循环与并发模型

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.thenqueueMicrotaskMutationObserver
  • 了解常见宏任务:setTimeoutsetInterval、I/O、UI 渲染
  • 掌握 requestAnimationFramerequestIdleCallback 的使用
  • 实战:输出顺序分析、异步调度器

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 可以:

  1. 将耗时操作(网络请求、定时器)交给宿主环境(浏览器/Node.js)处理
  2. 宿主环境完成后,将回调放入任务队列
  3. 主线程空闲时,从任务队列中取出回调执行

#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 每次事件循环取一个 setTimeoutsetInterval、I/O、UI 渲染
微任务 Microtask Queue 每个宏任务执行完后清空 Promise.thenqueueMicrotaskMutationObserver

关键规则

  1. 同步代码最先执行
  2. 微任务在当前宏任务结束后、下一个宏任务开始前执行
  3. 微任务会全部执行完毕后,才执行下一个宏任务
  4. 微任务执行过程中新增的微任务,会在当前轮次继续执行

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)

使用 requestIdleCallbacksetTimeout 将长任务拆分为小片段,避免阻塞主线程:

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.thenqueueMicrotask)在当前宏任务结束后全部清空,然后执行下一个宏任务(setTimeoutsetInterval、I/O);requestAnimationFrame 在渲染前执行适合动画,requestIdleCallback 在空闲时执行适合后台任务;理解这套机制是分析异步代码执行顺序、编写高性能应用的基础。


下篇预告第25章:Promise --- 异步编程的基石 --- 我们将深入 Promise 的三种状态、链式调用、静态方法,并手写一个完整的 Promise 实现。


作者 :xhmico | CSDN 博客https://blog.csdn.net/xhmico

参考资源The Modern JavaScript Tutorial | MDN Web Docs | ECMAScript 规范

相关推荐
ん贤1 小时前
怎么设计,才算一个优秀审计模块
大数据·开发语言·设计·审计
l1t1 小时前
测试用rust重写的postgresql: pgrust
开发语言·postgresql·rust
2501_948106912 小时前
计算机毕业设计之jsp-智慧旅游分享平台
java·开发语言·spark·汽车·课程设计·旅游
阿里云云原生2 小时前
Agent 不再是“玩具”!AgentScope Java 2.0 GA 发布:构建企业级分布式智能体底座
java·开发语言·分布式·agentscope
BIM云平台开发2 小时前
【App.vue里跟踪页面跳转和用户ID】
开发语言·前端·javascript
NiceCloud喜云2 小时前
ClaudeAPI 接入 n8n / Dify / Open WebUI 实战:Base URL 配置、调用示例与排错
开发语言·ai·chatgpt·aigc
庵中十三居士2 小时前
【纯AI无人工修改】AI Agent从0到1实战:50行Python手写核心循环,一次看懂所有Agent框架的底层逻辑
开发语言·人工智能·python
甄同学2 小时前
第二十篇:MCP协议集成,Claude Code如何扩展AI Agent的能力边界
开发语言·人工智能·qt
云空3 小时前
《Three.js 完整版3D魔方:带贴纸+中心固定+自动求解复原》
前端·javascript·3d·three.js