阅读视图

发现新文章,点击刷新页面。

全方位解释 JavaScript 执行机制(从底层到实战)

在 JavaScript 学习中,变量提升、作用域屏蔽等问题常常让初学者困惑。比如一段看似简单的代码,却能引出关于执行机制的深层思考。本文将以如下代码为例,从执行上下文调用栈的底层视角等等,完整拆解代码的执行流程,带你看透 JS 代码运行的核心逻辑。

一、JS 是如何执行的?

在 Chrome 浏览器中,JavaScript 的执行由 V8 引擎负责。
V8 在运行 JS 代码时分为两个阶段:

1️⃣ 编译阶段

在代码执行前的一刹那,V8 会:

工作内容:

  1. 语法分析
    检查语法错误(比如括号、花括号是否配对)。

  2. 变量提升(Hoisting)

    • var 声明的变量 → 提前创建并赋值为 undefined
    • 函数声明(function xxx(){}) → 整体提升(优先级最高)
  3. 创建执行上下文对象 (Execution Context Object)

    • 包含三部分:

      • 变量环境
      • 词法环境
      • 可执行代码
  4. 把执行上下文压入调用栈 (Call Stack)

    • 全局上下文 → 首先压栈
    • 函数被调用 → 创建新的函数上下文 → 压栈

2️⃣ 执行阶段

编译完后开始执行:

  1. 变量和函数声明已准备好
  2. 按代码顺序逐行执行
  3. 函数调用 → 创建新上下文 → 压栈
  4. 函数执行完毕 → 上下文出栈(销毁,等待垃圾回收)

二、执行上下文与调用栈

V8 通过一个叫做 调用栈(Call Stack) 的结构来管理代码执行过程。

我们可以把它想象成一个「任务清单」:

  1. 全局执行上下文(Global Execution Context) 首先创建并压入栈底;
  2. 当执行函数时,会创建一个新的函数执行上下文,并压入栈顶;
  3. 函数执行完毕后,从栈顶弹出(出栈);
  4. 栈顶总是代表当前正在执行的上下文。

JS 引擎启动后,会自动创建一个 全局执行上下文

此时,执行栈中只有它一个上下文

┌────────────────────┐ ← 栈顶
│ 全局执行上下文      │
└────────────────────┘ ← 栈底

✅ 所以,在创建全局执行上下文时,它既是第一个入栈的
也是当前栈顶的上下文

var a = 1;
function fn(a) {
  var a = 2;
  function a() {}
  var b = a;
  console.log(a);
}
fn(3);

调用栈变化示意:

阶段 栈顶内容 说明
初始 全局上下文 代码准备执行
调用 fn(3) fn 执行上下文 函数被调用,压入栈顶
执行完 fn 全局上下文 函数上下文出栈
程序结束 全局上下文销毁 页面关闭或脚本结束

① 程序开始 → 创建全局执行上下文

[ 全局执行上下文 ]
变量环境: { a: undefined, fn: <function> }
词法环境: {}
代码: 全局代码

执行到 a = 1; fn(3); 时:

名称
a 1
fn function

② 调用 fn(3) → 创建新的函数执行上下文

┌────────────────────┐ ← 栈顶(当前执行环境)
│ fn 执行上下文       │
├────────────────────┤
│ 全局执行上下文      │ ← 栈底
└────────────────────┘

JS 引擎调用函数 fn,于是创建 fn 的执行上下文,并压入栈顶

此时:

  • 全局还在栈中(没被销毁);

  • 但栈顶变成了 fn

  • JS 正在执行 fn 函数体的代码。

编译阶段:

逐步提升分析:

  1. 形参 a → 先在环境中占位

    a = 3 (调用时传入的参数)
    
  2. 发现函数声明 function a() {}
    提升并覆盖前面的 a

    a = function a() {}
    
  3. 发现 var a = 2;
    var a 部分已存在(被提升过),此时不会再声明,只会在执行阶段再赋值。

  4. 发现 var b;
    b = undefined

编译阶段结束后:

名称
a function a() {}
b undefined
fn 执行上下文
变量环境:
  a: function a(){}   // 函数声明覆盖形参
  b: undefined
词法环境:
  (空)
代码:
  var a = 2;
  function a() {}
  var b = a;
  console.log(a);

执行阶段:

  1. var a = 2; → a = 2(覆盖变量环境中的 a: function a(){})
  2. var b = a; → b = 2
  3. console.log(a); → 输出 2

然后函数执行完毕 → 出栈。


③ 回到全局上下文

调用栈恢复为:全局执行上下文

执行栈状态:
┌────────────────────────┐
│ fn 函数执行上下文       │ ← 出栈(弹出)
├────────────────────────┤
│ 全局执行上下文          │ ← 回到全局
└────────────────────────┘

最终执行栈:
┌────────────────────────┐
│ 全局执行上下文          │
└────────────────────────┘

程序执行结束。

三、函数表达式不会被提升

我们来看一个非常经典的坑:

func(); // ❌ ReferenceError
let func = () => {
  console.log('函数表达式不会提升');
}

1️⃣ 编译阶段:

  • 变量 func 被登记进 词法环境
  • 但由于是 let 声明,它尚未初始化
  • 此时 func 处于 暂时性死区(TDZ)

2️⃣ 执行阶段:

  • 执行到 func(); 时,JS 发现 func 尚未初始化;

  • 于是抛出:

    ReferenceError: Cannot access 'func' before initialization
    

对比 var

func(); // ❌ TypeError: func is not a function
var func = function() {}
  • var 提升会使 func 被初始化为 undefined
  • 调用时相当于 undefined()
  • 所以报的是 TypeError

✅ 结论:let / const 存在暂时性死区;var 会变量提升。

四、严格模式下的执行机制

'use strict';
var a = 1;
var a = 2;

许多人以为“严格模式会禁止重复声明”,但其实不然。

严格模式下:

  • var 依然允许重复声明
  • 只是禁止未声明变量直接使用;
  • 禁止 this 自动绑定到全局对象;
  • 禁止删除变量;
  • 禁止函数参数重名等。

所以上面的代码仍然能正常执行,最终 a = 2

只有 letconst 声明时,重复定义才会抛出错误。


五、拓展:严格模式的其他影响

特性 普通模式 严格模式
未声明直接赋值 自动创建全局变量 ❌ 报错
重复声明 var ✅ 允许 ✅ 允许
重复声明 let/const ❌ 报错 ❌ 报错
this 指向 全局对象(window) undefined
删除变量 静默失败 ❌ 报错
函数参数重名 ✅ 允许 ❌ 报错

六、JS 底层机制(内存):值类型与引用类型详解

// 基本数据类型(Number):存储在栈内存中
let num = 1;

// 引用数据类型(Object):栈内存存储引用地址,堆内存存储实际对象
let obj = { age: 18 };

image.png

1.简单数据类型

let num1 = 10;
let num2 = 20;
num1 = num2;
console.log(num1);

1️⃣ 编译阶段

  • JS 引擎在栈内存中为 num1num2 各分配一块空间;
  • 它们都属于简单数据类型(number)
  • 值直接存在栈中。

2️⃣ 执行阶段

num1 = num2;

这一步只是把 num2值 20 拷贝一份赋给 num1
它们之间完全没有引用关系

2.复杂数据类型

let obj1 = {age:18};

let obj2 = obj1;
console.log(obj2);

image.png

1️⃣ 编译阶段

JavaScript 引擎在栈内存中登记两个变量名:

obj1 → undefined
obj2 → undefined

(此时只是变量声明,还未赋值)


2️⃣ 执行阶段

开始一行行执行代码👇

let obj1 = { age: 18 };
  • 堆内存中创建一个对象 { age: 18 }
  • 假设它在堆内存中的地址是 0x12312
  • 然后在栈中保存 obj1 → 0x12312(也就是对象的引用地址)。

当前内存图:

栈内存:
obj1 → 0x12312

堆内存:
0x12312 → { age: 18 }
let obj2 = obj1;

并不会在堆中创建新对象;

只是把 obj1 的地址拷贝一份给 obj2;

所以现在两个变量都指向同一个堆内存对象。

内存示意图:

栈内存:
obj1 → 0x12312
obj2 → 0x12312

堆内存:
0x12312 → { age: 18 }
console.log(obj2);
  • 输出 obj2 当前指向的对象,即堆内存中地址 0x001 里的数据;
  • 结果:{ age: 18 }

🚨七、 JS 执行机制与内存总结

1️⃣ 执行机制

  • JS 由 V8 引擎执行,分为 编译阶段执行阶段
  • 编译阶段:创建执行上下文、变量提升、语法检查。
  • 执行阶段:按顺序执行代码,遇到函数会创建新的执行上下文压入调用栈
  • 函数执行完毕后,执行上下文从栈中弹出(退栈,释放内存)。

2️⃣ 数据类型与内存

类型 存储位置 保存内容 拷贝方式 是否共享
简单类型(Number、String、Boolean、null、undefined、Symbol、BigInt) 值拷贝 ❌ 否
复杂类型(Object、Array、Function) 栈 + 堆 地址 引用拷贝 ✅ 是

🔍参考文档:mdn

从代码到页面:HTML/CSS/JS 渲染全解析

我们每天打开浏览器浏览网页时,背后都藏着一套复杂却高效的渲染逻辑 —— 浏览器如何把一串 HTML/CSS/JS 字符串,变成我们看到的图文并茂、可交互的页面?本文结合实际代码案例,拆解渲染核心流程,同时揭秘语义化标签、CSS 优先级等知识点在渲染中的实际作用。

一、浏览器渲染的核心流程:从输入到输出

浏览器(以 Chrome 为例)的渲染本质是「数据转换 + 可视化」的过程,核心目标是:在 1 秒内完成 60 次绘制(60fps),让页面流畅无卡顿。整体流程可简化为 3 大步骤 + 2 棵关键树:

1. 输入:HTML/CSS/JS 原材料

  • HTML:定义页面「结构」(比如标题、段落、侧边栏)
  • CSS:定义页面「样式」(比如颜色、布局、字体)
  • JS:定义页面「交互」(比如点击事件、动态修改内容)

2. 核心流程:3 步构建可视化页面

🌳第一步:解析 HTML,构建 DOM 树

浏览器拿到 HTML 字符串后,不会直接处理文本,而是先把它转换成「树状结构」——DOM(Document Object Model,文档对象模型)。

  • 解析逻辑:从 <html> 根标签开始,递归遍历所有子标签(<head><body><div> 等),给每个标签、文本创建对应的「节点」,最终形成层级分明的 DOM 树。
  • 核心作用:DOM 树是页面结构的「抽象描述」,让浏览器和 JS 能轻松定位、操作页面元素(比如 document.getElementById('#root') 就是通过 DOM 树查找节点)。

🌳第二步:解析 CSS,构建 CSSOM 树

同理,CSS 字符串也会被解析成「树状结构」——CSSOM(CSS Object Model,CSS 对象模型)。

  • 解析逻辑:浏览器会遍历所有 CSS 规则(内联样式、内部样式、外部样式),将选择器(比如 #p7.highlightp)和对应的样式属性(color: redbackground: #fff)组织成树。
  • 核心作用:CSSOM 树记录了「如何给 DOM 节点上色、排版」,为后续「样式匹配」做准备。

🎯第三步:DOM + CSSOM → 渲染树 → 页面绘制

这是最终呈现的关键步骤:

  1. 样式匹配:浏览器遍历 DOM 树的每个节点,在 CSSOM 树中找到对应的样式规则(比如给 <p class="highlight" id="p7"> 匹配所有相关 CSS);
  2. 构建渲染树:筛选出可见节点(隐藏节点如 display: none 会被剔除),并将节点与匹配后的样式结合,形成「渲染树」;
  3. 布局(回流):计算渲染树中每个节点的位置、尺寸(比如主内容区占多少宽度、侧边栏在哪个位置);
  4. 绘制(重绘):根据布局结果,将节点的颜色、背景、文字等绘制到屏幕上,完成页面渲染。

输出:流畅的可视化页面

理想状态下,浏览器会以 60fps 的速度重复「布局 - 绘制」过程,让页面滚动、交互时无卡顿。而性能优化的核心,就是减少布局和绘制的开销。

二、代码实例:见证渲染流程的实际作用

结合两篇代码案例,我们能更直观理解渲染流程中的关键知识点:

案例 1:语义化标签与 DOM 树构建🌳

<!-- 语义化页面核心代码 -->
<header>
  <h1>HTML5语义化标签--技术博客</h1>
</header>
<div class="container">
  <main>
    <section>
      <h2>主要内容</h2>
      <p>HTML5的语义标签有助于SEO和无障碍访问</p>
    </section>
  </main>
  <aside class="aside-left">左侧导航</aside>
  <aside class="aside-right">右侧推荐</aside>
</div>
<footer>&copy;2025 . All rights reserved.</footer>

渲染中的关键作用:

  1. 语义化标签让 DOM 树更「易懂」:
  • 浏览器解析时,<header><main><section><aside><footer> 等标签会被识别为具有明确语义的节点,而非普通 <div>
  • 对搜索引擎(如百度蜘蛛)来说,语义化 DOM 树能快速定位核心内容(<main> 里的内容权重最高),直接提升 SEO 效果。
  1. 布局优化与渲染性能:
  • CSS 中用 display: flex 布局,让 .container 的子节点(main+ 两个 aside)在桌面端并排显示;
  • 移动端通过 @media (max-width: 768px) 改变 flex-direction: column,避免布局错乱 —— 这一步本质是「修改 CSSOM 树规则」,触发重新布局和绘制,但因语义化 DOM 树结构清晰,开销极小;
  • main { flex: 1 } 让主内容区占满剩余空间,order: -1 调整侧边栏顺序,这些都是通过 CSSOM 与 DOM 节点匹配后生效的。

👀其余部分代码解析(要点):

  • min-height: calc(100vh - 140px):设置最小高度,100vh是屏幕全屏高度,减去头部和底部的总高度(140px),确保中间内容区能占满屏幕中间的空间,不会因为内容少而留白。
  • .aside-left { order: -1 }:弹性布局的顺序调整!默认情况下,main 会排在前面(因为 HTML 里 main 写在 aside 前面),加了order: -1后,左侧边栏会排到 main 前面,实现 “左 - 中 - 右” 的布局。
  @media (max-width: 768px) {
    .container{
        flex-direction: column;
    }
    .aside-left {
        order: 1;
    }
    .aside-right {
        order: 2;
    }
    aside{
        width: 100%;
    }
  }

响应式适配(手机端专属样式):

  • @media (max-width: 768px):表示 “当屏幕宽度≤768px(手机、平板竖屏)时,下面的样式生效”;
  • flex-direction: column:让.container 里的子元素从上到下排列(不再并排),适配手机的窄屏幕;
  • aside-left { order: 1 } 和 aside-right { order: 2 }:调整侧边栏顺序,让主内容区排在最前面(手机用户先看核心内容);
  • aside { width: 100% }:侧边栏占满手机屏幕宽度,不会留空白。
  <div class="container">
    <main>
      <section>
        <h2>主要内容</h2>
        <p>这里是页面的核心内容区域
          <code>&lt;main&gt;</code><code>&lt;section&gt;</code>
          标签表现结构清晰
        </p>
        <p>HTML5的语义标签有助于SEO和无障碍访问</p>
      </section>
    </main>
  • <code> 标签专门用来显示代码,里面的&lt;<的转义字符(如果直接写<main>,浏览器会把它当成标签解析,不会显示出来,所以用&lt;表示 “小于号”)。

案例 2:CSS 优先级与样式匹配

<!-- CSS 优先级案例核心代码 -->
<p class="highlight" id="p7" style="color: red;">这段文字是什么颜色</p>

<style>
#p7 { color: pink; } /* ID 选择器 */
.highlight { color: green; } /* 类选择器 */
p { color: blue; } /* 标签选择器 */
</style>

渲染中的关键作用:

  1. 样式匹配的「优先级规则」:

    • 浏览器构建 CSSOM 树后,会给每个 CSS 规则分配「权重」:内联样式(1000 分)> ID 选择器(100 分)> 类选择器(10 分)> 标签选择器(1 分);
    • 给 <p> 节点匹配样式时,会优先选择权重最高的规则(内联样式 color: red),所以文字最终显示红色 —— 这一步是 CSSOM 与 DOM 节点匹配的核心逻辑。
  2. 对渲染性能的影响:

    • 选择器权重越高,匹配速度越快(比如 ID 选择器直接定位唯一节点,标签选择器需要遍历所有同类型节点);
    • 避免滥用复杂选择器(如 div > ul > li > a),会增加 CSSOM 与 DOM 匹配的开销,拖慢渲染速度。

✅三、拓展:如何优化渲染性能?

结合渲染流程和代码案例,我们能总结出 3 个核心优化方向:

1. 优化 DOM 树:语义化 + 精简结构

  • 优先使用 <header><main><nav> 等语义化标签,让 DOM 树结构清晰,减少浏览器解析和搜索引擎爬取的开销;
  • 避免嵌套过深(比如 div > div > div > div),会增加 DOM 遍历时间,建议嵌套层级不超过 4 层;
  • 主内容 <main> 放在 HTML 前面,让浏览器优先解析核心内容,提升首屏渲染速度(可通过 CSS order 调整视觉顺序)。

2. 优化 CSSOM 树:简化选择器 + 合理优先级

  • 避免复杂选择器,优先使用类选择器(.highlight),减少标签选择器(p)和后代选择器的使用;
  • 合理使用优先级:内联样式仅用于紧急样式,ID 选择器不滥用(一个页面 ID 唯一),避免使用 !important(会打乱优先级规则,增加调试难度);
  • 外部 CSS 用 <link> 引入(而非 <style> 内嵌),让浏览器并行下载 CSS,不阻塞 DOM 解析。

3. 减少回流与重绘

  • 避免频繁修改 DOM 样式(比如 element.style.color = 'red'),可通过添加 / 移除类名批量修改样式;
  • 固定元素尺寸(比如侧边栏 width: 250px),减少布局计算开销;
  • 隐藏不可见元素(用 visibility: hidden 而非 display: none,前者仅重绘不回流)。

四、总结

浏览器渲染页面的本质,是将 HTML/CSS/JS 转换成 DOM 树和 CSSOM 树,再通过匹配、布局、绘制形成可视化页面。而我们写的每一行代码,都会影响这两棵树的构建效率:

  • 语义化 HTML 让 DOM 树更「易懂」,提升 SEO 和渲染效率;
  • 精简 CSS 让 CSSOM 树更「轻便」,加快样式匹配速度;
  • 合理的交互逻辑让 JS 不阻塞渲染,提升页面流畅度。
  • 内联样式(style 属性) > ID 选择器 > 类选择器(.xxx) > 标签选择器(元素名)

彻底搞懂 CSS 盒子模型 box-sizing:小白也能看懂的布局核心

在 CSS 布局中,“盒子模型” 是绕不开的基础概念 —— 新手写布局时总遇到 “元素明明设了宽度,却还是撑破容器”“两个盒子死活不能同行排列” 的问题,本质都是没搞懂盒子模型的计算逻辑。今天就结合实际代码,从基础到进阶,把标准盒模型和怪异盒模型讲透,让小白也能精准控制元素布局!

一、先搞懂:页面上的 “盒子” 到底占多大地方?

不管是 div、span 还是 img,页面上所有元素都能看作一个 “盒子”。这个盒子的实际占位大小,由 4 个核心部分组成(从内到外):

  1. 内容区(content) :盒子的核心,用来放文字、图片等内容,我们用 width/height 直接设置;
  2. 内边距(padding) :内容区和边框之间的空隙,比如给按钮加 padding 能让文字不紧贴边框;
  3. 边框(border) :盒子的 “边框线”,border 的宽度和样式会影响盒子的视觉大小;
  4. 外边距(margin) :盒子和其他元素之间的 “空隙”,用来控制元素间距,不影响盒子自身大小。

⚠️ 关键提醒:margin 是盒子 “外部的空隙”,永远不包含在盒子自身的 “宽高” 里,只影响盒子和其他元素的距离;而 contentpaddingborder 才是决定盒子 “自身大小” 的核心 —— 这也是两种盒模型的核心区别所在!

二、两种盒子模型:标准盒模型 vs 怪异盒模型

CSS 中盒子模型分为两种,核心差异是「width/height 到底包含哪些部分」,我们结合之前的实际代码来拆解(重点!)。

先回顾之前的代码核心配置:

.container {
  width: 1200px; /* 父容器固定宽度 */
  margin: 0 auto; /* 水平居中 */
}
.box {
  width: 580px;    /* 设定宽度 */
  height: 100px;   /* 设定高度 */
  padding: 5px;    /* 内边距:上下左右各5px */
  border: 1px solid #000; /* 边框:上下左右各1px */
  margin: 0 10px;  /* 外边距:左右各10px */
  box-sizing: border-box; /* 怪异盒模型 */
  display: inline-block; /* 行内块,实现同行排列 */
}

1. 标准盒模型(默认):box-sizing: content-box

这是浏览器默认的盒模型,新手最容易踩坑的地方!

  • 核心规则width/height 只等于「内容区(content)」的大小,padding 和 border 会额外撑开盒子的总宽高。
  • 实际大小计算(用上面的代码举例):盒子自身总宽度 = 内容区宽度(width:580px) + padding 左右(5px+5px) + border 左右(1px+1px)= 580 + 10 + 2 = 592px;盒子在页面的占位宽度 = 自身总宽度(592px) + margin 左右(10px+10px)= 592 + 20 = 612px;
  • 布局坑点:如果父容器 .container 宽度是 1200px,两个这样的盒子占位总宽度是 612px×2=1224px,超过父容器宽度,第二个盒子会被挤到下一行,布局直接错乱!

简单说:标准盒模型下,width 只是 “内容的宽度”,padding 和 border 会让盒子 “变大”,新手很难精准控制尺寸。

2. 怪异盒模型(推荐):box-sizing: border-box

也叫 IE 盒模型(最早由 IE 浏览器实现,后来成为通用标准),是实际开发中必用的盒模型!

  • 核心规则width/height 已经包含了「内容区(content)+ padding + border」,padding 和 border 会向内压缩内容区,而不会让盒子自身变大。
  • 实际大小计算(还是用上面的代码):盒子自身总宽度 = width:580px(已经包含 content + padding + border);内容区实际宽度 = 设定宽度(580px) - padding 左右(10px) - border 左右(2px)= 568px;盒子在页面的占位宽度 = 自身总宽度(580px) + margin 左右(20px)= 600px;
  • 布局优势:两个盒子的占位总宽度是 600px×2=1200px,刚好填满父容器 .container,完美实现同行排列 —— 这也是之前的代码为什么要设置 box-sizing: border-box 的原因!

简单说:怪异盒模型下,width 就是盒子 “最终的自身宽度”,padding 和 border 只会挤内部内容,不会影响外部布局,尺寸控制超精准。

🎯一张表对比,再也不混淆:

盒模型类型 width/height 包含范围 盒子自身总宽计算方式 新手友好度 实际开发使用率
标准盒模型(默认) 仅内容区(content) content + padding + border ❌ 容易踩坑 10%(极少用)
怪异盒模型(推荐) content + padding + border 直接等于设定的 width ✅ 精准可控 90%(必用)

三、小白实战:为什么一定要用怪异盒模型?

举个新手最常遇到的场景:做一个 300px 宽的按钮,带 2px 边框和 15px 内边距。

用标准盒模型(坑!):

.button {
  width: 300px;
  padding: 15px;
  border: 2px solid #000;
  /* box-sizing: content-box; 默认 */
}
  • 按钮实际宽度 = 300(content) + 15×2(padding) + 2×2(border)= 334px;
  • 如果你父容器是 300px 宽,按钮会直接撑破容器,超出显示!

✅用怪异盒模型(稳!):

.button {
  width: 300px;
  padding: 15px;
  border: 2px solid #000;
  box-sizing: border-box; /* 关键设置 */
}
  • 按钮实际宽度 = 300px(包含 content + padding + border);
  • 内容区宽度 = 300 - 30(padding) - 4(border)= 266px;
  • 按钮完美适配 300px 父容器,不管怎么调 padding 和 border,按钮总宽都不变!

四、进阶技巧:全局统一盒模型(实战必用)

实际开发中,我们不会给每个元素单独设置 box-sizing: border-box,而是用通配符选择器全局统一,避免遗漏:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box; /* 全局启用怪异盒模型 */
}
  • 作用:让页面所有元素都使用怪异盒模型,布局时不用反复计算 padding 和 border,效率翻倍;
  • 补充:如果个别元素需要用标准盒模型,再单独给它设置 box-sizing: content-box 即可(极少场景)。

五、深入拓展:盒模型和文档流、定位的关系(进阶)

小白掌握上面的内容已经能应对 80% 布局,但想进阶还要知道:盒模型的 “占位计算” 会受「文档流」和「定位」影响:

1. 文档流中的元素(默认情况)

像 div(块级元素)、inline-block(行内块元素),都会遵循盒模型的占位规则 ——margin 会影响周围元素的位置(比如块级元素的 margin-top 会推开上方元素)。

2. 脱离文档流的元素(position 特殊值)

当元素设置 position: absolute(绝对定位)或 position: fixed(固定定位)时:

  • 元素会脱离文档流,不再占据原来的 “位置”,margin 不会影响其他元素的布局;
  • 但元素自身的大小计算,依然遵循 box-sizing 的规则(比如 width:200px + box-sizing: border-box,自身总宽还是 200px);
  • 实际占位由 top/left 等属性控制,但自身大小的计算逻辑不变。

举个例子:

.box {
  position: absolute;
  top: 100px;
  left: 100px;
  width: 200px;
  padding: 20px;
  border: 3px solid #000;
  box-sizing: border-box;
}
  • 元素自身总宽 = 200px(包含 content + padding + border);
  • 内容区宽度 = 200 - 40(padding) - 6(border)= 154px;
  • 元素会定位在页面 (100px, 100px) 处,自身大小不受文档流影响,但盒模型计算规则没变。

✅总结:新手必记的 3 个要点

  1. 页面上所有元素都是 “盒子”,占位由 content + padding + border + margin 组成;
  2. 浏览器默认是标准盒模型,容易踩坑,实际开发必用 box-sizing: border-box(怪异盒模型);
  3. 全局设置 * { box-sizing: border-box },能让布局尺寸精准可控,效率翻倍。

掌握盒子模型,你就能解决 80% 的 CSS 布局问题 —— 后续学习 Flex、Grid 等高级布局,也需要以盒子模型为基础。现在就打开代码编辑器,试试修改 paddingborder 看看两种盒模型的差异,实践一次就再也忘不掉!

🔁 JavaScript中的迭代全攻略 - for/while/迭代器/生成器/for await...of详解

🎯 学习目标:全面掌握 JavaScript 的迭代语句与迭代协议,能在不同数据结构与同步/异步场景下选择正确的遍历方式,并写出高性能、可维护的代码。

📊 难度等级:中级
🏷️ 技术标签:#JavaScript #迭代器 #生成器 #for-of #for-in #for await...of
⏱️ 阅读时间:约9分钟


🌟 引言

在日常 JavaScript 开发中,遍历数据“看起来都差不多”,但一旦涉及对象、Map/Set、类数组、异步数据流、性能优化,就容易踩坑:

  • 数组用 for...in 导致顺序和性能问题;
  • 把对象当可迭代结构用 for...of 直接报错;
  • 自定义迭代器和生成器不会用,错过优雅的惰性计算;
  • 大数据流用 for await...of 才是正确姿势,却不熟悉协议细节。

今天我用 7 个核心技巧,系统讲清“语句与协议”的边界与最佳实践,帮你写出更高效、可控的迭代代码。


💡 核心技巧详解

1. for vs while:基础循环的选择

🔍 应用场景

索引驱动的数组遍历、需要精细控制起止与步长、或在条件驱动下执行循环。

❌ 常见问题

while 忘记更新计数器,或数组越界导致死循环/错误。

// ❌ 计数器更新缺失可能导致死循环
let i = 0;
while (i < 3) {
  console.log('loop:', i);
  // 缺失 i++
}

✅ 推荐方案

/**
 * 使用 for 循环安全遍历数组
 * @param {any[]} list - 任意元素数组
 * @returns {any[]} 同步收集的结果
 */
const traverseWithFor = (list) => {
  const results = [];
  for (let i = 0; i < list.length; i++) {
    // 索引明确,性能稳定
    results.push(list[i]);
  }
  return results;
};

/**
 * 使用 while 循环在条件驱动下遍历
 * @param {number} start - 起始值
 * @param {number} end - 结束值(不含)
 * @returns {number[]} 生成的序列
 */
const traverseWithWhile = (start, end) => {
  const seq = [];
  let i = start;
  while (i < end) {
    seq.push(i);
    i += 1; // 确保条件推进
  }
  return seq;
};

💡 核心要点

  • for 适合索引明确的顺序遍历;
  • while 适合条件驱动的循环,注意推进条件;
  • 数据量大时优先 for,可微调步长与边界以获得稳定性能。

2. for...of vs for...in:遍历语义不要混用

🔍 应用场景

for...of 用于“可迭代对象”(数组、字符串、Map、Set、生成器等);for...in 用于对象的“可枚举属性键”。

❌ 常见问题

对数组使用 for...in 导致遍历到原型属性、顺序不稳定;对普通对象使用 for...of 直接报错。

// ❌ 对数组使用 for...in(遍历索引字符串,顺序可能受影响)
const arr = [10, 20, 30];
for (const k in arr) {
  console.log('index string:', k); // '0', '1', '2'
}

// ❌ 对对象使用 for...of(非可迭代,抛错)
// for (const v of { a: 1 }) {} // TypeError: {} is not iterable

✅ 推荐方案

/**
 * 遍历可迭代对象(数组/字符串/Map/Set)
 * @param {Iterable<any>} iterable - 可迭代对象
 * @returns {any[]} 收集到的值
 */
const collectIterableValues = (iterable) => {
  const values = [];
  for (const v of iterable) {
    values.push(v);
  }
  return values;
};

/**
 * 遍历对象自有可枚举属性键
 * @param {Record<string, any>} obj - 普通对象
 * @returns {string[]} 键列表
 */
const collectOwnKeys = (obj) => {
  const keys = [];
  for (const k in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, k)) keys.push(k);
  }
  return keys;
};

💡 核心要点

  • for...of 读取“值”;for...in 读取“键”;
  • 对象遍历更推荐 Object.keys/values/entries 保持可控与可读;
  • 避免对数组使用 for...in,不稳定且性能较差。

3. 迭代协议:可迭代与迭代器

🔍 应用场景

自定义数据结构的迭代行为,支持惰性求值与可控遍历。

✅ 推荐方案

/**
 * 自定义可迭代范围 [start, end)
 * @param {number} start - 起始
 * @param {number} end - 结束(不含)
 * @returns {Iterable<number>} 可迭代范围
 */
const createRange = (start, end) => ({
  [Symbol.iterator]: () => {
    let i = start;
    return {
      next: () => (i < end ? { value: i++, done: false } : { value: undefined, done: true })
    };
  }
});

💡 核心要点

  • 可迭代对象需实现 [Symbol.iterator] 返回迭代器;
  • 迭代器需实现 next() 返回 { value, done }
  • 惰性迭代避免一次性创建大量中间数据,利于性能与内存。

4. 生成器(Generator):更优雅的迭代器写法

🔍 应用场景

用更简洁的语法生成迭代序列,支持 yield 惰性输出与中断。

/**
 * 生成器创建斐波那契序列(前 n 个)
 * @param {number} n - 个数
 * @returns {Iterable<number>} 斐波那契序列
 */
const fibonacci = function* (n) {
  let a = 0, b = 1, i = 0;
  while (i < n) {
    yield a;
    [a, b] = [b, a + b];
    i += 1;
  }
};

💡 核心要点

  • 生成器本质是迭代器,语法更简洁;
  • yield 提供惰性产生值的能力;
  • 可搭配 for...of 直接遍历。

5. 异步迭代与 for await...of:数据流的正确遍历

🔍 应用场景

分页加载、流式文件读写、网络请求批次处理等异步数据源。

/**
 * 异步生成器:模拟批次拉取数据
 * @param {number} batches - 批次数
 * @returns {AsyncIterable<number>} 异步可迭代数据
 */
const fetchBatches = async function* (batches) {
  for (let i = 1; i <= batches; i++) {
    await new Promise((r) => setTimeout(r, 5));
    yield i; // 每批返回一个结果
  }
};

/**
 * 使用 for await...of 收集异步迭代结果
 * @param {AsyncIterable<any>} asyncIterable - 异步可迭代
 * @returns {Promise<any[]>} 收集到的值
 */
const collectAsync = async (asyncIterable) => {
  const out = [];
  for await (const chunk of asyncIterable) {
    out.push(chunk);
  }
  return out;
};

💡 核心要点

  • for await...of 遍历 AsyncIterable/AsyncGenerator
  • 避免将 Promise[] 直接用 for await...of,应先 Promise.all 或转为异步迭代;
  • 异步迭代是处理大数据流的内存友好方案。

6. 遍历不同数据结构:Map/Set/字符串/类数组

/**
 * 遍历 Map/Set/字符串/类数组的统一收集器
 * @param {any} target - 目标数据结构
 * @returns {any[]} 收集到的值
 */
const collectValues = (target) => {
  if (target instanceof Map) return Array.from(target.entries());
  if (target instanceof Set) return Array.from(target.values());
  if (typeof target === 'string') return Array.from(target);
  if (typeof target.length === 'number') return Array.from(target); // NodeList/HTMLCollection
  return [];
};

💡 核心要点

  • Map 默认遍历 [key, value]Set 遍历值;
  • 字符串是可迭代对象,可被 for...of 按字符遍历;
  • 类数组(如 NodeList)可用 Array.from 转正,避免 for...in

7. 性能与边界:选择正确的迭代策略

🎯 实战建议

  • 大数组性能更稳定的通常是索引 for
  • 需要可读性与语义清晰,优先 for...of
  • 对象遍历优先 Object.keys/entries,避免 for...in 的原型链干扰;
  • 大数据流与分页拉取用 for await...of,避免一次性内存爆炸;
  • 迭代中修改容器(增删元素)要谨慎,优先生成快照或用惰性策略。

📊 技巧对比总结

技巧 使用场景 优势 注意事项
for 索引驱动数组 性能稳定,可控 注意边界与步长
while 条件驱动循环 灵活 防止死循环
for...of 可迭代对象 语义清晰 不适用于普通对象
for...in 对象键遍历 简单 避免用于数组,过滤原型链
迭代协议 自定义结构 惰性、可控 正确实现 next()
生成器 简洁迭代器 语法优雅 正确使用 yield
for await...of 异步数据流 内存友好 仅用于异步可迭代

🎯 实战应用建议

最佳实践

  1. 使用 for...of 遍历集合类型,提升语义与可读性。
  2. 对象遍历使用 Object.entriesfor...of 组合处理键值对。
  3. 大数据流统一封装为 AsyncGenerator,用 for await...of 消费。
  4. 根据性能需求选择 forfor...of,避免对数组使用 for...in

性能考虑

  • 大数组在热路径中优先 for
  • 遍历中避免闭包捕获大对象,及时释放引用;
  • 生成器/异步生成器的惰性策略可显著降低内存峰值。

💡 总结

这 7 个迭代技巧覆盖了“语句与协议”的完整谱系:从 for/whilefor...of/for...in,再到迭代器/生成器与异步迭代。掌握它们后,你的代码将:

  1. 更语义化,遍历意图清晰;
  2. 更高性能,避免不必要的中间数据与错误用法;
  3. 更易维护,统一封装迭代行为并复用。

🔗 相关资源


💡 今日收获:理解迭代语句与协议的边界,选择合适的遍历方式,写出可读、可控、性能稳定的迭代代码。

如果这篇文章对你有帮助,欢迎点赞、收藏和分享!有任何问题也欢迎在评论区讨论。 🚀

🛠️ Service Worker API深度解析 - 生命周期、缓存与离线实战

🎯 学习目标:掌握 Service Worker 的生命周期、缓存策略与请求拦截,并能为 Web 应用实现稳定的离线与更新机制。

📊 难度等级:中级
🏷️ 技术标签#ServiceWorker #PWA #CacheStorage #离线
⏱️ 阅读时间:约9分钟


🌟 引言

在日常的 Web 开发中,你是否遇到过这样的困扰:

  • 用户在弱网或断网下无法使用你的应用;
  • 新版本上线后,用户缓存未更新,看到旧资源;
  • 资源请求策略混乱,缓存越来越大且不可控;
  • 对 Service Worker 的生命周期与作用域理解不清,调试成本高。

今天分享6个「Service Worker API」的核心实战技巧,帮助你构建离线可用、可控更新、性能更稳的现代 Web 应用!


💡 核心技巧详解

📝 使用说明:本文从基础到实战,围绕注册、生命周期、缓存、拦截、更新与通信展开,示例均采用箭头函数与 JSDoc 注释,代码简短易读。

1. 注册与作用域控制:让 SW 管控正确的路径

🔍 应用场景

在单页或多页应用中,确保 Service Worker 的作用域覆盖到需要离线与拦截的目录,并避免影响不相关的区域。

❌ 常见问题

sw.js 放在错误目录导致作用域过大或过小,或忘记使用 localhost/HTTPS 导致无法注册。

/**
 * 注册 Service Worker(作用域为当前目录)
 * @returns {Promise<ServiceWorkerRegistration|undefined>} 注册结果
 */
const registerServiceWorker = async () => {
  if (!('serviceWorker' in navigator)) return undefined;
  // 作用域以 sw.js 所在路径为准,建议放在要管控的子目录根部
  return navigator.serviceWorker.register('./sw.js');
};

// 页面加载时注册
void registerServiceWorker();

💡 核心要点

  • 作用域 = sw.js 所在目录及其子路径;
  • localhost 视为安全上下文,可直接注册;生产环境需 HTTPS
  • 建议按子系统或页面组划分多个 SW,避免过度管控。

🎯 实际应用

sw.js 放在 app/ 目录即可仅管控 app/* 路径;管理后台与用户端可各自独立。


2. 生命周期:安装/激活/更新与强制切换

🔍 应用场景

新版本上线时,控制旧 SW 与新 SW 的切换节奏,保障用户体验与兼容性。

✅ 推荐方案

install 做预缓存,在 activate 清理旧缓存。用 skipWaiting() 加速新版本进入等待状态,用 clients.claim() 让新版本接管已有页面。

/**
 * 安装阶段:预缓存核心资源
 * @param {ExtendableEvent} event
 */
self.addEventListener('install', (event) => {
  const precache = async () => {
    const cache = await caches.open('sw-cache-v1');
    await cache.addAll(['./', './index.html', './ping.txt']);
  };
  event.waitUntil(precache());
  self.skipWaiting(); // 加速激活新版本
});

/**
 * 激活阶段:清理旧缓存并接管页面
 * @param {ExtendableEvent} event
 */
self.addEventListener('activate', (event) => {
  const cleanup = async () => {
    const keys = await caches.keys();
    await Promise.all(keys.filter((k) => k !== 'sw-cache-v1').map((k) => caches.delete(k)));
    await self.clients.claim(); // 接管现有客户端
  };
  event.waitUntil(cleanup());
});

💡 核心要点

  • 新 SW 下载后进入「等待」状态,待旧 SW释放页面才激活;
  • skipWaiting() 可加速切换,但需评估对未保存状态的影响;
  • clients.claim() 让新 SW 立即控制已有页面,避免「刷新后才生效」。

3. 缓存策略:预缓存 + 运行时缓存(Cache First)

🔍 应用场景

静态资源预缓存,接口或动态资源按需缓存,常用策略:Cache First + 后台更新或 Stale-While-Revalidate

/**
 * 运行时缓存:同源 GET 请求采用 Cache First
 * @param {FetchEvent} event
 */
self.addEventListener('fetch', (event) => {
  const handle = async () => {
    const req = event.request;
    const url = new URL(req.url);
    const isGetSameOrigin = req.method === 'GET' && url.origin === self.location.origin;
    if (!isGetSameOrigin) return fetch(req);

    const cached = await caches.match(req);
    if (cached) return cached; // 命中缓存直接返回

    const res = await fetch(req);
    const cache = await caches.open('sw-cache-v1');
    // 克隆响应再入缓存,避免流耗尽
    void cache.put(req, res.clone());
    return res;
  };
  event.respondWith(handle());
});

💡 核心要点

  • 只缓存同源 GET 请求,避免跨域与非幂等请求带来风险;
  • cache.put(req, res.clone()) 保持流可读;
  • 大型资源建议分层缓存与过期清理。

4. 请求拦截与降级:离线兜底与错误处理

🔍 应用场景

网络不可用或请求失败时,友好降级与兜底响应。

/**
 * 兜底策略:失败时返回离线页面或提示文案
 * @param {FetchEvent} event
 */
self.addEventListener('fetch', (event) => {
  const fallback = async () => {
    try {
      return await fetch(event.request);
    } catch (_) {
      const offline = new Response('当前离线,稍后重试。', { status: 200, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
      return offline;
    }
  };
  event.respondWith(fallback());
});

💡 核心要点

  • 将兜底逻辑限定在关键路径与纯文本提示;
  • 更复杂场景可返回离线 HTML 页面;
  • 结合本地数据(IndexedDB)实现离线阅读或表单缓存。

5. 版本管理与缓存清理:控制可预测的更新

🔍 应用场景

频繁迭代时,确保缓存不会无限增长,且用户能及时获得新版本。

/**
 * 简洁的版本化命名与清理
 * @returns {Promise<void>} 完成清理
 */
const purgeOldCaches = async () => {
  const keep = 'sw-cache-v2';
  const keys = await caches.keys();
  await Promise.all(keys.filter((k) => k !== keep).map((k) => caches.delete(k)));
};

💡 核心要点

  • 缓存名带版本号,更新时切换并清理旧版本;
  • 资源 URL 带 hash/版本号,避免命名冲突;
  • 定期在激活阶段执行清理。

6. 与页面通信:状态提示与精准控制

🔍 应用场景

通知页面 SW 的安装/更新状态,或接收页面指令(清理缓存、强制更新等)。

/**
 * 页面向 SW 发送消息(如请求清理缓存)
 * @param {any} payload - 发送的数据
 * @returns {void}
 */
const postToSW = (payload) => {
  if (!navigator.serviceWorker.controller) return;
  navigator.serviceWorker.controller.postMessage(payload);
};

// SW 内接收消息
self.addEventListener('message', (event) => {
  /** @type {{type: string}} */
  const data = event.data || {};
  if (data.type === 'PURGE') void caches.keys().then((keys) => keys.forEach((k) => caches.delete(k)));
});

💡 核心要点

  • 通过 postMessage 双向通信;
  • 结合 UI 提示用户是否有新版本可用;
  • 更新策略透明,保障用户体验。

📊 技巧对比总结

技巧 使用场景 优势 注意事项
注册与作用域 控制 SW 管控范围 覆盖精准、易维护 路径决定作用域;需 HTTPS/localhost
生命周期 安装/激活/更新 版本切换可控 skipWaiting/claim 需评估风险
运行时缓存 动态资源加速 性能稳定、离线可用 谨慎缓存非幂等请求
离线兜底 弱网/断网场景 体验友好 离线页面需轻量且易识别
版本与清理 持续迭代 可预测更新 版本化命名 + 激活清理
通信 更新提示/控制 可视化与可操作 注意安全与消息协议

🎯 实战应用建议

  1. 对不同子系统使用独立 SW,降低影响面。
  2. 预缓存仅包含核心静态资源,运行时缓存针对关键接口与静态文件。
  3. 同源 GET 才进入缓存,其他请求原样透传。
  4. 使用 skipWaiting + clients.claim 组合时,加入“新版本可用”提示与用户确认。
  5. 缓存名版本化,激活阶段统一清理旧缓存。
  6. 通过 postMessage 汇报状态与接收页面指令,形成可观察的更新流程。

性能考虑

  • 限制缓存体积并分层管理,避免无限增长;
  • 为接口使用 Stale-While-Revalidate 或带超时时间的 Cache First
  • 对跨域与非幂等请求不做缓存,降低风险。

💡 总结

这6个 Service Worker 实战技巧覆盖从注册到更新的完整闭环,掌握它们能让你的 Web 应用:

  1. 在断网与弱网中保持基本可用;
  2. 版本更新可控、缓存不膨胀;
  3. 请求策略清晰、性能稳定;
  4. 调试成本降低、维护更可预测。

🔗 相关资源


💡 今日收获:掌握了 Service Worker 的核心用法与最佳实践,这些能力能显著提升 Web 应用的可用性与性能。

如果这篇文章对你有帮助,欢迎点赞、收藏和分享!有任何问题也欢迎在评论区讨论。 🚀

「周更第10期」实用JS库推荐:VueUse

引言

在Vue 3的生态系统中,组合式API(Composition API)为我们带来了更灵活的代码组织方式。而VueUse作为Vue生态中最受欢迎的工具库之一,为开发者提供了200多个实用的组合式函数,极大地简化了日常开发工作。本期我们将深入探索VueUse这个强大的工具库。

库介绍

基本信息

  • 库名称:VueUse
  • GitHub Stars:19.5k+ ⭐
  • 维护状态:活跃维护,定期更新
  • 兼容性:Vue 2.7+ / Vue 3.0+
  • 包大小:Tree-shakable,按需引入
  • 依赖关系:基于Vue响应式系统

核心特点

VueUse是一个基于Vue组合式API的实用工具库,它将常见的开发需求封装成可复用的组合式函数。主要特点包括:

  1. 丰富的功能集合:提供200+个实用函数,涵盖状态管理、浏览器API、动画、网络请求等各个方面
  2. 类型安全:完整的TypeScript支持,提供优秀的开发体验
  3. Tree-shakable:支持按需引入,不会增加不必要的包体积
  4. SSR友好:完美支持服务端渲染
  5. 灵活可配置:大部分函数都提供丰富的配置选项
  6. 响应式设计:充分利用Vue的响应式系统

安装使用

安装命令

# 使用 pnpm(推荐)
pnpm add @vueuse/core

# 安装额外的集成包
pnpm add @vueuse/components @vueuse/integrations

基础使用示例

<template>
  <div>
    <p>鼠标位置: {{ x }}, {{ y }}</p>
    <p>窗口大小: {{ width }} x {{ height }}</p>
    <button @click="toggle">
      {{ isOnline ? '在线' : '离线' }}
    </button>
  </div>
</template>

<script setup>
import { useMouse, useWindowSize, useOnline } from '@vueuse/core'

// 获取鼠标位置
const { x, y } = useMouse()

// 获取窗口大小
const { width, height } = useWindowSize()

// 检测网络状态
const { isOnline, toggle } = useOnline()
</script>

VueUse API分类详解

1. 状态管理类

useLocalStorage / useSessionStorage

// 响应式的本地存储
const name = useLocalStorage('user-name', '默认值')
const settings = useSessionStorage('app-settings', { theme: 'light' })

useToggle

// 布尔值切换
const [isVisible, toggle] = useToggle()
const [status, toggleStatus] = useToggle('active', 'inactive')

useCounter

// 计数器管理
const { count, inc, dec, set, reset } = useCounter(0, { min: 0, max: 100 })

2. 浏览器API类

useMouse / useMousePressed

// 鼠标位置和状态
const { x, y } = useMouse()
const { pressed } = useMousePressed()

useKeyboard / useKeyModifier

// 键盘事件
const { ctrl, shift, alt, meta } = useKeyModifier()
const keys = useKeyboard()

useClipboard

// 剪贴板操作
const { text, copy, copied, isSupported } = useClipboard()

useFullscreen

// 全屏控制
const { isFullscreen, enter, exit, toggle } = useFullscreen()

3. 传感器类

useDeviceOrientation

// 设备方向
const { alpha, beta, gamma, absolute } = useDeviceOrientation()

useGeolocation

// 地理位置
const { coords, locatedAt, error } = useGeolocation()

useBattery

// 电池状态
const { charging, level, dischargingTime } = useBattery()

4. 网络类

useFetch

// HTTP请求
const { data, error, isFetching } = useFetch('/api/users')

useWebSocket

// WebSocket连接
const { status, data, send, open, close } = useWebSocket('ws://localhost:8080')

5. 动画类

useTransition

// 数值过渡动画
const source = ref(0)
const output = useTransition(source, {
  duration: 1000,
  transition: TransitionPresets.easeInOutCubic
})

useInterval / useTimeout

// 定时器管理
const { pause, resume, isActive } = useInterval(() => {
  console.log('每秒执行')
}, 1000)

6. 元素操作类

useElementSize

// 元素尺寸监听
const el = ref()
const { width, height } = useElementSize(el)

useIntersectionObserver

// 元素可见性检测
const target = ref()
const { isIntersecting } = useIntersectionObserver(target)

useResizeObserver

// 元素大小变化监听
const el = ref()
useResizeObserver(el, (entries) => {
  console.log('元素大小改变')
})

7. 实用工具类

useDebounce / useThrottle

// 防抖和节流
const input = ref('')
const debouncedValue = useDebounce(input, 500)
const throttledValue = useThrottle(input, 1000)

useAsyncState

// 异步状态管理
const { state, isReady, isLoading, error, execute } = useAsyncState(
  () => fetchUserData(),
  null
)

useVModel

// 双向绑定辅助
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const data = useVModel(props, 'modelValue', emit)

实际应用场景

1. 响应式布局系统

<template>
  <div class="responsive-layout">
    <aside v-if="!isMobile" class="sidebar">
      侧边栏内容
    </aside>
    <main :class="{ 'full-width': isMobile }">
      <header>
        <button @click="toggleTheme">
          {{ isDark ? '🌙' : '☀️' }}
        </button>
      </header>
      <div class="content">
        主要内容区域
      </div>
    </main>
  </div>
</template>

<script setup>
import { useBreakpoints, useDark, useToggle } from '@vueuse/core'

// 响应式断点
const breakpoints = useBreakpoints({
  mobile: 768,
  tablet: 1024,
  desktop: 1280
})

const isMobile = breakpoints.smaller('tablet')

// 暗黑模式
const isDark = useDark()
const toggleTheme = useToggle(isDark)
</script>

2. 实时数据监控面板

<template>
  <div class="dashboard">
    <div class="status-bar">
      <span :class="{ online: isOnline, offline: !isOnline }">
        {{ isOnline ? '在线' : '离线' }}
      </span>
      <span>电池: {{ batteryLevel }}%</span>
      <span>{{ formattedTime }}</span>
    </div>
    
    <div class="charts">
      <canvas ref="chartCanvas"></canvas>
    </div>
    
    <div class="controls">
      <button @click="startMonitoring" :disabled="isMonitoring">
        开始监控
      </button>
      <button @click="stopMonitoring" :disabled="!isMonitoring">
        停止监控
      </button>
    </div>
  </div>
</template>

<script setup>
import { 
  useOnline, 
  useBattery, 
  useNow, 
  useInterval,
  useElementSize 
} from '@vueuse/core'

// 网络状态
const isOnline = useOnline()

// 电池状态
const { level: batteryLevel } = useBattery()

// 实时时间
const now = useNow()
const formattedTime = computed(() => 
  now.value.toLocaleTimeString()
)

// 图表容器
const chartCanvas = ref()
const { width, height } = useElementSize(chartCanvas)

// 监控控制
const isMonitoring = ref(false)
const { pause, resume } = useInterval(() => {
  // 更新图表数据
  updateChartData()
}, 1000, { immediate: false })

const startMonitoring = () => {
  isMonitoring.value = true
  resume()
}

const stopMonitoring = () => {
  isMonitoring.value = false
  pause()
}
</script>

3. 高级表单处理

<template>
  <form @submit.prevent="handleSubmit">
    <div class="form-group">
      <input 
        v-model="form.email" 
        type="email" 
        placeholder="邮箱"
        :class="{ error: emailError }"
      >
      <span v-if="emailError" class="error-text">
        {{ emailError }}
      </span>
    </div>
    
    <div class="form-group">
      <input 
        v-model="form.password" 
        type="password" 
        placeholder="密码"
      >
    </div>
    
    <button 
      type="submit" 
      :disabled="!isFormValid || isSubmitting"
    >
      {{ isSubmitting ? '提交中...' : '登录' }}
    </button>
    
    <div v-if="submitError" class="error">
      {{ submitError }}
    </div>
  </form>
</template>

<script setup>
import { 
  useVModel, 
  useAsyncValidator, 
  useAsyncState,
  useDebounce 
} from '@vueuse/core'

// 表单数据
const form = reactive({
  email: '',
  password: ''
})

// 邮箱验证(防抖)
const debouncedEmail = useDebounce(() => form.email, 500)
const { pass: isEmailValid, errorMessage: emailError } = useAsyncValidator(
  debouncedEmail,
  async (value) => {
    if (!value) return '邮箱不能为空'
    if (!/\S+@\S+\.\S+/.test(value)) return '邮箱格式不正确'
    
    // 异步验证邮箱是否已注册
    const exists = await checkEmailExists(value)
    if (!exists) return '邮箱未注册'
    
    return true
  }
)

// 表单验证
const isFormValid = computed(() => 
  isEmailValid.value && form.password.length >= 6
)

// 提交处理
const { 
  state: submitResult, 
  isLoading: isSubmitting, 
  error: submitError, 
  execute: submitForm 
} = useAsyncState(
  () => loginUser(form),
  null,
  { immediate: false }
)

const handleSubmit = () => {
  if (isFormValid.value) {
    submitForm()
  }
}
</script>

优缺点分析

优势

  1. 开发效率高:提供大量开箱即用的功能,减少重复代码编写
  2. 类型安全:完整的TypeScript支持,减少运行时错误
  3. 性能优秀:基于Vue响应式系统,性能表现良好
  4. 文档完善:官方文档详细,示例丰富
  5. 社区活跃:持续更新,bug修复及时
  6. 灵活性强:大部分函数都支持自定义配置

局限性

  1. 学习成本:函数众多,需要时间熟悉各个API
  2. 包体积:虽然支持tree-shaking,但完整引入会增加包体积
  3. Vue绑定:仅适用于Vue项目,不能在其他框架中使用
  4. 版本依赖:需要Vue 2.7+或Vue 3.0+

最佳实践建议

1. 按需引入

// 推荐:按需引入
import { useMouse, useLocalStorage } from '@vueuse/core'

// 避免:全量引入
import * as VueUse from '@vueuse/core'

2. 合理使用响应式

// 合理利用响应式特性
const { x, y } = useMouse()
const position = computed(() => `${x.value}, ${y.value}`)

// 避免不必要的响应式转换
const staticConfig = { timeout: 5000 } // 静态配置无需响应式

3. 错误处理

const { data, error, isFinished } = useFetch('/api/data')

watchEffect(() => {
  if (error.value) {
    console.error('请求失败:', error.value)
    // 处理错误逻辑
  }
})

总结

VueUse是Vue生态系统中不可或缺的工具库,它通过提供丰富的组合式函数,极大地提升了Vue开发的效率和体验。无论是处理浏览器API、状态管理,还是实现复杂的交互效果,VueUse都能提供优雅的解决方案。

推荐使用场景:

  • Vue 3项目的快速开发
  • 需要大量浏览器API交互的应用
  • 追求代码复用性和可维护性的项目
  • 需要响应式状态管理的复杂应用

VueUse不仅是一个工具库,更是学习Vue组合式API最佳实践的优秀范例。通过研究其源码和使用方式,能够帮助开发者更好地理解和运用Vue 3的核心特性。

相关链接

🌐 HTML DOM API全攻略(下篇)- 高级接口与现代Web开发实践

📚 学习目标

  • 掌握HTML DOM API的后9个高级接口
  • 理解现代Web开发中的复杂应用场景
  • 学会构建高性能、可扩展的Web应用
  • 掌握前沿技术的实际应用方法

🎯 难度等级

高级 - 适合有扎实JavaScript基础和Web开发经验的开发者

🏷️ 技术标签

JavaScript DOM API Web开发 性能优化 现代前端

⏱️ 阅读时间

约18-25分钟


🚀 引言

在上篇文章中,我们深入探讨了HTML DOM API的前9个核心接口。本篇将继续这一技术之旅,重点介绍更加高级和专业的API接口,这些接口是构建现代Web应用不可或缺的技术基础。

从多线程处理到实时通信,从3D图形渲染到音频处理,这些高级API将帮助你构建更加强大和用户友好的Web应用。

🎯 高级API详解

10. Web Worker接口:多线程处理的利器

🔍 应用场景

大数据处理、复杂计算、图像处理、后台任务

❌ 常见问题

// ❌ 主线程执行耗时操作,阻塞UI
function processLargeData(data) {
    let result = [];
    for (let i = 0; i < data.length; i++) {
        // 复杂计算
        result.push(heavyComputation(data[i]));
    }
    return result;
}

// UI会被阻塞
const result = processLargeData(largeDataSet);
updateUI(result);

✅ 推荐方案

// ✅ Web Worker管理器
class WorkerManager {
    constructor() {
        this.workers = new Map();
        this.taskQueue = [];
        this.maxWorkers = navigator.hardwareConcurrency || 4;
    }
    
    /**
     * 创建Worker
     * @param {string} name - Worker名称
     * @param {string} scriptPath - Worker脚本路径
     * @returns {Promise<Worker>}
     */
    async createWorker(name, scriptPath) {
        try {
            const worker = new Worker(scriptPath);
            
            // 设置错误处理
            worker.onerror = (error) => {
                console.error(`Worker ${name} error:`, error);
                this.removeWorker(name);
            };
            
            // 设置消息处理
            worker.onmessage = (event) => {
                this.handleWorkerMessage(name, event);
            };
            
            this.workers.set(name, {
                worker,
                busy: false,
                tasks: new Map()
            });
            
            return worker;
        } catch (error) {
            console.error('Failed to create worker:', error);
            throw error;
        }
    }
    
    /**
     * 执行任务
     * @param {string} workerName - Worker名称
     * @param {string} taskType - 任务类型
     * @param {*} data - 任务数据
     * @returns {Promise}
     */
    async executeTask(workerName, taskType, data) {
        const workerInfo = this.workers.get(workerName);
        if (!workerInfo) {
            throw new Error(`Worker ${workerName} not found`);
        }
        
        const taskId = this.generateTaskId();
        
        return new Promise((resolve, reject) => {
            // 存储任务回调
            workerInfo.tasks.set(taskId, { resolve, reject });
            
            // 发送任务到Worker
            workerInfo.worker.postMessage({
                taskId,
                type: taskType,
                data
            });
            
            workerInfo.busy = true;
        });
    }
    
    /**
     * 处理Worker消息
     * @param {string} workerName - Worker名称
     * @param {MessageEvent} event - 消息事件
     */
    handleWorkerMessage(workerName, event) {
        const { taskId, result, error } = event.data;
        const workerInfo = this.workers.get(workerName);
        
        if (!workerInfo) return;
        
        const task = workerInfo.tasks.get(taskId);
        if (!task) return;
        
        // 清理任务
        workerInfo.tasks.delete(taskId);
        workerInfo.busy = false;
        
        // 执行回调
        if (error) {
            task.reject(new Error(error));
        } else {
            task.resolve(result);
        }
    }
    
    /**
     * 批量处理任务
     * @param {Array} tasks - 任务数组
     * @param {string} workerScript - Worker脚本
     * @returns {Promise<Array>}
     */
    async processBatch(tasks, workerScript) {
        const results = [];
        const workers = [];
        
        // 创建Worker池
        for (let i = 0; i < Math.min(this.maxWorkers, tasks.length); i++) {
            const workerName = `batch-worker-${i}`;
            await this.createWorker(workerName, workerScript);
            workers.push(workerName);
        }
        
        // 分配任务
        const promises = tasks.map((task, index) => {
            const workerName = workers[index % workers.length];
            return this.executeTask(workerName, task.type, task.data);
        });
        
        try {
            const results = await Promise.all(promises);
            return results;
        } finally {
            // 清理Worker
            workers.forEach(name => this.removeWorker(name));
        }
    }
    
    /**
     * 移除Worker
     * @param {string} name - Worker名称
     */
    removeWorker(name) {
        const workerInfo = this.workers.get(name);
        if (workerInfo) {
            workerInfo.worker.terminate();
            this.workers.delete(name);
        }
    }
    
    /**
     * 生成任务ID
     * @returns {string}
     */
    generateTaskId() {
        return `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    }
    
    /**
     * 清理所有Worker
     */
    cleanup() {
        for (const [name] of this.workers) {
            this.removeWorker(name);
        }
    }
}

// Worker脚本示例 (data-processor.js)
const workerScript = `
self.onmessage = function(event) {
    const { taskId, type, data } = event.data;
    
    try {
        let result;
        
        switch (type) {
            case 'processData':
                result = processLargeDataSet(data);
                break;
            case 'imageFilter':
                result = applyImageFilter(data);
                break;
            case 'calculation':
                result = performComplexCalculation(data);
                break;
            default:
                throw new Error('Unknown task type: ' + type);
        }
        
        self.postMessage({ taskId, result });
    } catch (error) {
        self.postMessage({ taskId, error: error.message });
    }
};

function processLargeDataSet(data) {
    return data.map(item => {
        // 复杂数据处理逻辑
        return {
            ...item,
            processed: true,
            timestamp: Date.now()
        };
    });
}

function applyImageFilter(imageData) {
    const { data: pixels, width, height } = imageData;
    
    // 应用灰度滤镜
    for (let i = 0; i < pixels.length; i += 4) {
        const gray = pixels[i] * 0.299 + pixels[i + 1] * 0.587 + pixels[i + 2] * 0.114;
        pixels[i] = gray;     // Red
        pixels[i + 1] = gray; // Green
        pixels[i + 2] = gray; // Blue
        // Alpha channel (i + 3) remains unchanged
    }
    
    return { data: pixels, width, height };
}

function performComplexCalculation(numbers) {
    return numbers.reduce((acc, num) => {
        // 模拟复杂计算
        for (let i = 0; i < 1000000; i++) {
            acc += Math.sqrt(num * i);
        }
        return acc;
    }, 0);
}
`;

// 使用示例
const workerManager = new WorkerManager();

// 创建数据处理Worker
await workerManager.createWorker('dataProcessor', 'data-processor.js');

// 处理大数据集
const largeData = Array.from({ length: 10000 }, (_, i) => ({ id: i, value: Math.random() }));

try {
    const result = await workerManager.executeTask('dataProcessor', 'processData', largeData);
    console.log('数据处理完成:', result);
    updateUI(result);
} catch (error) {
    console.error('数据处理失败:', error);
}

// 批量处理任务
const tasks = [
    { type: 'calculation', data: [1, 2, 3, 4, 5] },
    { type: 'calculation', data: [6, 7, 8, 9, 10] },
    { type: 'calculation', data: [11, 12, 13, 14, 15] }
];

const batchResults = await workerManager.processBatch(tasks, 'data-processor.js');
console.log('批量处理结果:', batchResults);

11. WebRTC接口:实时通信的核心

🔍 应用场景

视频通话、音频聊天、屏幕共享、P2P数据传输

❌ 常见问题

// ❌ 简单的WebRTC实现,缺乏错误处理和连接管理
const pc = new RTCPeerConnection();
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream => {
        pc.addStream(stream);
    });

✅ 推荐方案

// ✅ 专业的WebRTC通信管理器
class WebRTCManager {
    constructor(options = {}) {
        this.options = {
            iceServers: [
                { urls: 'stun:stun.l.google.com:19302' },
                { urls: 'stun:stun1.l.google.com:19302' }
            ],
            ...options
        };
        
        this.localStream = null;
        this.remoteStream = null;
        this.peerConnection = null;
        this.dataChannel = null;
        this.isInitiator = false;
        this.listeners = new Map();
    }
    
    /**
     * 初始化WebRTC连接
     * @returns {Promise}
     */
    async initialize() {
        try {
            this.peerConnection = new RTCPeerConnection({
                iceServers: this.options.iceServers
            });
            
            this.setupPeerConnectionEvents();
            return true;
        } catch (error) {
            console.error('WebRTC初始化失败:', error);
            throw error;
        }
    }
    
    /**
     * 设置PeerConnection事件监听
     */
    setupPeerConnectionEvents() {
        // ICE候选事件
        this.peerConnection.onicecandidate = (event) => {
            if (event.candidate) {
                this.emit('iceCandidate', event.candidate);
            }
        };
        
        // 连接状态变化
        this.peerConnection.onconnectionstatechange = () => {
            const state = this.peerConnection.connectionState;
            this.emit('connectionStateChange', state);
            
            if (state === 'failed') {
                this.handleConnectionFailure();
            }
        };
        
        // 远程流接收
        this.peerConnection.ontrack = (event) => {
            this.remoteStream = event.streams[0];
            this.emit('remoteStream', this.remoteStream);
        };
        
        // 数据通道接收
        this.peerConnection.ondatachannel = (event) => {
            const channel = event.channel;
            this.setupDataChannelEvents(channel);
            this.emit('dataChannel', channel);
        };
    }
    
    /**
     * 获取用户媒体
     * @param {Object} constraints - 媒体约束
     * @returns {Promise<MediaStream>}
     */
    async getUserMedia(constraints = { video: true, audio: true }) {
        try {
            this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
            
            // 添加轨道到PeerConnection
            this.localStream.getTracks().forEach(track => {
                this.peerConnection.addTrack(track, this.localStream);
            });
            
            this.emit('localStream', this.localStream);
            return this.localStream;
        } catch (error) {
            console.error('获取用户媒体失败:', error);
            throw error;
        }
    }
    
    /**
     * 获取屏幕共享
     * @returns {Promise<MediaStream>}
     */
    async getDisplayMedia() {
        try {
            const screenStream = await navigator.mediaDevices.getDisplayMedia({
                video: true,
                audio: true
            });
            
            // 替换视频轨道
            const videoTrack = screenStream.getVideoTracks()[0];
            const sender = this.peerConnection.getSenders().find(s => 
                s.track && s.track.kind === 'video'
            );
            
            if (sender) {
                await sender.replaceTrack(videoTrack);
            }
            
            // 监听屏幕共享结束
            videoTrack.onended = () => {
                this.stopScreenShare();
            };
            
            this.emit('screenShare', screenStream);
            return screenStream;
        } catch (error) {
            console.error('获取屏幕共享失败:', error);
            throw error;
        }
    }
    
    /**
     * 停止屏幕共享
     */
    async stopScreenShare() {
        try {
            // 恢复摄像头
            const videoTrack = this.localStream.getVideoTracks()[0];
            const sender = this.peerConnection.getSenders().find(s => 
                s.track && s.track.kind === 'video'
            );
            
            if (sender && videoTrack) {
                await sender.replaceTrack(videoTrack);
            }
            
            this.emit('screenShareStopped');
        } catch (error) {
            console.error('停止屏幕共享失败:', error);
        }
    }
    
    /**
     * 创建Offer
     * @returns {Promise<RTCSessionDescription>}
     */
    async createOffer() {
        try {
            this.isInitiator = true;
            
            // 创建数据通道
            this.dataChannel = this.peerConnection.createDataChannel('messages', {
                ordered: true
            });
            this.setupDataChannelEvents(this.dataChannel);
            
            const offer = await this.peerConnection.createOffer();
            await this.peerConnection.setLocalDescription(offer);
            
            return offer;
        } catch (error) {
            console.error('创建Offer失败:', error);
            throw error;
        }
    }
    
    /**
     * 创建Answer
     * @param {RTCSessionDescription} offer - 远程Offer
     * @returns {Promise<RTCSessionDescription>}
     */
    async createAnswer(offer) {
        try {
            await this.peerConnection.setRemoteDescription(offer);
            
            const answer = await this.peerConnection.createAnswer();
            await this.peerConnection.setLocalDescription(answer);
            
            return answer;
        } catch (error) {
            console.error('创建Answer失败:', error);
            throw error;
        }
    }
    
    /**
     * 设置远程描述
     * @param {RTCSessionDescription} answer - 远程Answer
     */
    async setRemoteAnswer(answer) {
        try {
            await this.peerConnection.setRemoteDescription(answer);
        } catch (error) {
            console.error('设置远程Answer失败:', error);
            throw error;
        }
    }
    
    /**
     * 添加ICE候选
     * @param {RTCIceCandidate} candidate - ICE候选
     */
    async addIceCandidate(candidate) {
        try {
            await this.peerConnection.addIceCandidate(candidate);
        } catch (error) {
            console.error('添加ICE候选失败:', error);
        }
    }
    
    /**
     * 设置数据通道事件
     * @param {RTCDataChannel} channel - 数据通道
     */
    setupDataChannelEvents(channel) {
        channel.onopen = () => {
            this.emit('dataChannelOpen', channel);
        };
        
        channel.onmessage = (event) => {
            this.emit('dataChannelMessage', event.data);
        };
        
        channel.onclose = () => {
            this.emit('dataChannelClose');
        };
        
        channel.onerror = (error) => {
            console.error('数据通道错误:', error);
            this.emit('dataChannelError', error);
        };
    }
    
    /**
     * 发送数据
     * @param {*} data - 要发送的数据
     */
    sendData(data) {
        if (this.dataChannel && this.dataChannel.readyState === 'open') {
            const message = typeof data === 'string' ? data : JSON.stringify(data);
            this.dataChannel.send(message);
        } else {
            console.warn('数据通道未打开');
        }
    }
    
    /**
     * 处理连接失败
     */
    async handleConnectionFailure() {
        console.log('连接失败,尝试重新连接...');
        
        // 重新创建ICE连接
        this.peerConnection.restartIce();
        
        this.emit('connectionFailure');
    }
    
    /**
     * 关闭连接
     */
    close() {
        // 停止本地流
        if (this.localStream) {
            this.localStream.getTracks().forEach(track => track.stop());
        }
        
        // 关闭数据通道
        if (this.dataChannel) {
            this.dataChannel.close();
        }
        
        // 关闭PeerConnection
        if (this.peerConnection) {
            this.peerConnection.close();
        }
        
        this.emit('closed');
    }
    
    /**
     * 事件监听
     * @param {string} event - 事件名
     * @param {Function} callback - 回调函数
     */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    /**
     * 触发事件
     * @param {string} event - 事件名
     * @param {*} data - 事件数据
     */
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(callback => callback(data));
    }
}

// 使用示例
const rtcManager = new WebRTCManager();

// 初始化WebRTC
await rtcManager.initialize();

// 监听事件
rtcManager.on('localStream', (stream) => {
    document.getElementById('localVideo').srcObject = stream;
});

rtcManager.on('remoteStream', (stream) => {
    document.getElementById('remoteVideo').srcObject = stream;
});

rtcManager.on('dataChannelMessage', (message) => {
    console.log('收到消息:', message);
});

// 发起通话
document.getElementById('startCall').addEventListener('click', async () => {
    await rtcManager.getUserMedia();
    const offer = await rtcManager.createOffer();
    // 通过信令服务器发送offer
    sendToSignalingServer({ type: 'offer', offer });
});

// 接听通话
document.getElementById('answerCall').addEventListener('click', async () => {
    await rtcManager.getUserMedia();
    // 假设从信令服务器收到offer
    const answer = await rtcManager.createAnswer(receivedOffer);
    // 通过信令服务器发送answer
    sendToSignalingServer({ type: 'answer', answer });
});

12. WebGL接口:3D图形渲染的强大工具

🔍 应用场景

3D游戏、数据可视化、CAD应用、虚拟现实

❌ 常见问题

// ❌ 直接使用WebGL API,代码复杂难维护
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

✅ 推荐方案

// ✅ WebGL渲染引擎
class WebGLRenderer {
    constructor(canvas, options = {}) {
        this.canvas = canvas;
        this.gl = this.initWebGL();
        this.programs = new Map();
        this.buffers = new Map();
        this.textures = new Map();
        this.uniforms = new Map();
        
        this.options = {
            clearColor: [0.0, 0.0, 0.0, 1.0],
            enableDepthTest: true,
            ...options
        };
        
        this.setupWebGL();
    }
    
    /**
     * 初始化WebGL上下文
     * @returns {WebGLRenderingContext}
     */
    initWebGL() {
        const gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
        
        if (!gl) {
            throw new Error('WebGL not supported');
        }
        
        return gl;
    }
    
    /**
     * 设置WebGL基本配置
     */
    setupWebGL() {
        const { gl, options } = this;
        
        // 设置清除颜色
        gl.clearColor(...options.clearColor);
        
        // 启用深度测试
        if (options.enableDepthTest) {
            gl.enable(gl.DEPTH_TEST);
            gl.depthFunc(gl.LEQUAL);
        }
        
        // 设置视口
        this.resize();
    }
    
    /**
     * 创建着色器
     * @param {string} source - 着色器源码
     * @param {number} type - 着色器类型
     * @returns {WebGLShader}
     */
    createShader(source, type) {
        const { gl } = this;
        const shader = gl.createShader(type);
        
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            const error = gl.getShaderInfoLog(shader);
            gl.deleteShader(shader);
            throw new Error(`Shader compilation error: ${error}`);
        }
        
        return shader;
    }
    
    /**
     * 创建着色器程序
     * @param {string} vertexSource - 顶点着色器源码
     * @param {string} fragmentSource - 片段着色器源码
     * @param {string} name - 程序名称
     * @returns {WebGLProgram}
     */
    createProgram(vertexSource, fragmentSource, name) {
        const { gl } = this;
        
        const vertexShader = this.createShader(vertexSource, gl.VERTEX_SHADER);
        const fragmentShader = this.createShader(fragmentSource, gl.FRAGMENT_SHADER);
        
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            const error = gl.getProgramInfoLog(program);
            gl.deleteProgram(program);
            throw new Error(`Program linking error: ${error}`);
        }
        
        // 清理着色器
        gl.deleteShader(vertexShader);
        gl.deleteShader(fragmentShader);
        
        // 存储程序
        this.programs.set(name, program);
        
        return program;
    }
    
    /**
     * 创建缓冲区
     * @param {ArrayBuffer|Float32Array} data - 数据
     * @param {number} type - 缓冲区类型
     * @param {number} usage - 使用方式
     * @param {string} name - 缓冲区名称
     * @returns {WebGLBuffer}
     */
    createBuffer(data, type = this.gl.ARRAY_BUFFER, usage = this.gl.STATIC_DRAW, name) {
        const { gl } = this;
        const buffer = gl.createBuffer();
        
        gl.bindBuffer(type, buffer);
        gl.bufferData(type, data, usage);
        
        if (name) {
            this.buffers.set(name, { buffer, type, size: data.length });
        }
        
        return buffer;
    }
    
    /**
     * 创建纹理
     * @param {HTMLImageElement|HTMLCanvasElement} image - 图像源
     * @param {string} name - 纹理名称
     * @returns {WebGLTexture}
     */
    createTexture(image, name) {
        const { gl } = this;
        const texture = gl.createTexture();
        
        gl.bindTexture(gl.TEXTURE_2D, texture);
        
        // 设置纹理参数
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        
        // 上传纹理数据
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        
        if (name) {
            this.textures.set(name, texture);
        }
        
        return texture;
    }
    
    /**
     * 设置uniform变量
     * @param {WebGLProgram} program - 着色器程序
     * @param {string} name - uniform名称
     * @param {*} value - 值
     */
    setUniform(program, name, value) {
        const { gl } = this;
        const location = gl.getUniformLocation(program, name);
        
        if (location === null) return;
        
        if (Array.isArray(value)) {
            switch (value.length) {
                case 1:
                    gl.uniform1f(location, value[0]);
                    break;
                case 2:
                    gl.uniform2fv(location, value);
                    break;
                case 3:
                    gl.uniform3fv(location, value);
                    break;
                case 4:
                    gl.uniform4fv(location, value);
                    break;
                case 16:
                    gl.uniformMatrix4fv(location, false, value);
                    break;
            }
        } else if (typeof value === 'number') {
            gl.uniform1f(location, value);
        }
    }
    
    /**
     * 绑定属性
     * @param {WebGLProgram} program - 着色器程序
     * @param {string} name - 属性名称
     * @param {WebGLBuffer} buffer - 缓冲区
     * @param {number} size - 组件数量
     * @param {number} type - 数据类型
     */
    bindAttribute(program, name, buffer, size = 3, type = this.gl.FLOAT) {
        const { gl } = this;
        const location = gl.getAttribLocation(program, name);
        
        if (location === -1) return;
        
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.enableVertexAttribArray(location);
        gl.vertexAttribPointer(location, size, type, false, 0, 0);
    }
    
    /**
     * 渲染场景
     * @param {Object} scene - 场景对象
     */
    render(scene) {
        const { gl } = this;
        
        // 清除画布
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        
        // 渲染每个对象
        scene.objects.forEach(object => {
            this.renderObject(object);
        });
    }
    
    /**
     * 渲染单个对象
     * @param {Object} object - 渲染对象
     */
    renderObject(object) {
        const { gl } = this;
        const program = this.programs.get(object.program);
        
        if (!program) return;
        
        // 使用着色器程序
        gl.useProgram(program);
        
        // 设置uniforms
        Object.entries(object.uniforms || {}).forEach(([name, value]) => {
            this.setUniform(program, name, value);
        });
        
        // 绑定属性
        Object.entries(object.attributes || {}).forEach(([name, attr]) => {
            const buffer = this.buffers.get(attr.buffer);
            if (buffer) {
                this.bindAttribute(program, name, buffer.buffer, attr.size, attr.type);
            }
        });
        
        // 绑定纹理
        if (object.texture) {
            const texture = this.textures.get(object.texture);
            if (texture) {
                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, texture);
                this.setUniform(program, 'u_texture', 0);
            }
        }
        
        // 绘制
        if (object.indices) {
            const indexBuffer = this.buffers.get(object.indices);
            if (indexBuffer) {
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);
                gl.drawElements(object.mode || gl.TRIANGLES, indexBuffer.size, gl.UNSIGNED_SHORT, 0);
            }
        } else {
            const vertexBuffer = this.buffers.get(object.vertices);
            if (vertexBuffer) {
                gl.drawArrays(object.mode || gl.TRIANGLES, 0, vertexBuffer.size / 3);
            }
        }
    }
    
    /**
     * 调整画布大小
     */
    resize() {
        const { canvas, gl } = this;
        const displayWidth = canvas.clientWidth;
        const displayHeight = canvas.clientHeight;
        
        if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
            canvas.width = displayWidth;
            canvas.height = displayHeight;
            gl.viewport(0, 0, displayWidth, displayHeight);
        }
    }
    
    /**
     * 清理资源
     */
    cleanup() {
        const { gl } = this;
        
        // 删除程序
        this.programs.forEach(program => gl.deleteProgram(program));
        
        // 删除缓冲区
        this.buffers.forEach(({ buffer }) => gl.deleteBuffer(buffer));
        
        // 删除纹理
        this.textures.forEach(texture => gl.deleteTexture(texture));
    }
}

// 使用示例
const canvas = document.getElementById('webgl-canvas');
const renderer = new WebGLRenderer(canvas);

// 顶点着色器
const vertexShaderSource = `
    attribute vec3 a_position;
    attribute vec2 a_texCoord;
    
    uniform mat4 u_modelViewMatrix;
    uniform mat4 u_projectionMatrix;
    
    varying vec2 v_texCoord;
    
    void main() {
        gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(a_position, 1.0);
        v_texCoord = a_texCoord;
    }
`;

// 片段着色器
const fragmentShaderSource = `
    precision mediump float;
    
    uniform sampler2D u_texture;
    uniform float u_time;
    
    varying vec2 v_texCoord;
    
    void main() {
        vec4 color = texture2D(u_texture, v_texCoord);
        color.rgb *= 0.5 + 0.5 * sin(u_time);
        gl_FragColor = color;
    }
`;

// 创建着色器程序
renderer.createProgram(vertexShaderSource, fragmentShaderSource, 'basic');

// 创建立方体顶点数据
const vertices = new Float32Array([
    // 前面
    -1, -1,  1,
     1, -1,  1,
     1,  1,  1,
    -1,  1,  1,
    // 后面
    -1, -1, -1,
    -1,  1, -1,
     1,  1, -1,
     1, -1, -1
]);

const indices = new Uint16Array([
    0, 1, 2,   0, 2, 3,    // 前面
    4, 5, 6,   4, 6, 7,    // 后面
    5, 0, 3,   5, 3, 6,    // 左面
    1, 4, 7,   1, 7, 2,    // 右面
    3, 2, 7,   3, 7, 6,    // 上面
    5, 4, 1,   5, 1, 0     // 下面
]);

// 创建缓冲区
renderer.createBuffer(vertices, renderer.gl.ARRAY_BUFFER, renderer.gl.STATIC_DRAW, 'vertices');
renderer.createBuffer(indices, renderer.gl.ELEMENT_ARRAY_BUFFER, renderer.gl.STATIC_DRAW, 'indices');

// 创建场景
const scene = {
    objects: [{
        program: 'basic',
        vertices: 'vertices',
        indices: 'indices',
        attributes: {
            a_position: { buffer: 'vertices', size: 3 }
        },
        uniforms: {
            u_modelViewMatrix: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,-5,1],
            u_projectionMatrix: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1],
            u_time: 0
        }
    }]
};

// 渲染循环
function animate(time) {
    scene.objects[0].uniforms.u_time = time * 0.001;
    renderer.render(scene);
    requestAnimationFrame(animate);
}

animate(0);

13. Web Audio接口:音频处理的专业方案

🔍 应用场景

音频播放器、音效处理、音乐制作、语音识别

❌ 常见问题

// ❌ 简单的音频播放,功能有限
const audio = new Audio('music.mp3');
audio.play();
audio.volume = 0.5;

✅ 推荐方案

// ✅ 专业音频处理引擎
class AudioEngine {
    constructor() {
        this.context = null;
        this.masterGain = null;
        this.sources = new Map();
        this.effects = new Map();
        this.isInitialized = false;
    }
    
    /**
     * 初始化音频上下文
     * @returns {Promise}
     */
    async initialize() {
        try {
            // 创建音频上下文
            this.context = new (window.AudioContext || window.webkitAudioContext)();
            
            // 创建主音量控制
            this.masterGain = this.context.createGain();
            this.masterGain.connect(this.context.destination);
            
            // 恢复音频上下文(某些浏览器需要用户交互)
            if (this.context.state === 'suspended') {
                await this.context.resume();
            }
            
            this.isInitialized = true;
            console.log('音频引擎初始化成功');
        } catch (error) {
            console.error('音频引擎初始化失败:', error);
            throw error;
        }
    }
    
    /**
     * 加载音频文件
     * @param {string} url - 音频文件URL
     * @param {string} name - 音频名称
     * @returns {Promise<AudioBuffer>}
     */
    async loadAudio(url, name) {
        try {
            const response = await fetch(url);
            const arrayBuffer = await response.arrayBuffer();
            const audioBuffer = await this.context.decodeAudioData(arrayBuffer);
            
            this.sources.set(name, audioBuffer);
            return audioBuffer;
        } catch (error) {
            console.error(`加载音频失败 ${url}:`, error);
            throw error;
        }
    }
    
    /**
     * 播放音频
     * @param {string} name - 音频名称
     * @param {Object} options - 播放选项
     * @returns {AudioBufferSourceNode}
     */
    playAudio(name, options = {}) {
        if (!this.isInitialized) {
            console.warn('音频引擎未初始化');
            return null;
        }
        
        const audioBuffer = this.sources.get(name);
        if (!audioBuffer) {
            console.warn(`音频 ${name} 未找到`);
            return null;
        }
        
        const source = this.context.createBufferSource();
        source.buffer = audioBuffer;
        
        // 创建音量控制
        const gainNode = this.context.createGain();
        gainNode.gain.value = options.volume || 1.0;
        
        // 连接音频图
        source.connect(gainNode);
        gainNode.connect(this.masterGain);
        
        // 设置播放参数
        if (options.loop) {
            source.loop = true;
        }
        
        if (options.playbackRate) {
            source.playbackRate.value = options.playbackRate;
        }
        
        // 开始播放
        const startTime = options.when || this.context.currentTime;
        const offset = options.offset || 0;
        const duration = options.duration || audioBuffer.duration;
        
        source.start(startTime, offset, duration);
        
        // 设置结束回调
        if (options.onEnded) {
            source.onended = options.onEnded;
        }
        
        return source;
    }
    
    /**
     * 创建音频效果器
     * @param {string} type - 效果器类型
     * @param {Object} params - 参数
     * @returns {AudioNode}
     */
    createEffect(type, params = {}) {
        let effect;
        
        switch (type) {
            case 'reverb':
                effect = this.createReverb(params);
                break;
            case 'delay':
                effect = this.createDelay(params);
                break;
            case 'filter':
                effect = this.createFilter(params);
                break;
            case 'distortion':
                effect = this.createDistortion(params);
                break;
            case 'compressor':
                effect = this.createCompressor(params);
                break;
            default:
                console.warn(`未知效果器类型: ${type}`);
                return null;
        }
        
        return effect;
    }
    
    /**
     * 创建混响效果
     * @param {Object} params - 混响参数
     * @returns {ConvolverNode}
     */
    createReverb(params = {}) {
        const convolver = this.context.createConvolver();
        
        // 创建冲激响应
        const length = params.length || this.context.sampleRate * 2;
        const impulse = this.context.createBuffer(2, length, this.context.sampleRate);
        
        for (let channel = 0; channel < 2; channel++) {
            const channelData = impulse.getChannelData(channel);
            for (let i = 0; i < length; i++) {
                const decay = Math.pow(1 - i / length, params.decay || 2);
                channelData[i] = (Math.random() * 2 - 1) * decay;
            }
        }
        
        convolver.buffer = impulse;
        return convolver;
    }
    
    /**
     * 创建延迟效果
     * @param {Object} params - 延迟参数
     * @returns {Object}
     */
    createDelay(params = {}) {
        const delay = this.context.createDelay(params.maxDelay || 1.0);
        const feedback = this.context.createGain();
        const wetGain = this.context.createGain();
        const dryGain = this.context.createGain();
        const output = this.context.createGain();
        
        // 设置参数
        delay.delayTime.value = params.delayTime || 0.3;
        feedback.gain.value = params.feedback || 0.3;
        wetGain.gain.value = params.wet || 0.5;
        dryGain.gain.value = params.dry || 0.5;
        
        // 连接节点
        delay.connect(feedback);
        feedback.connect(delay);
        delay.connect(wetGain);
        wetGain.connect(output);
        dryGain.connect(output);
        
        return {
            input: delay,
            output: output,
            dryGain: dryGain
        };
    }
    
    /**
     * 创建滤波器
     * @param {Object} params - 滤波器参数
     * @returns {BiquadFilterNode}
     */
    createFilter(params = {}) {
        const filter = this.context.createBiquadFilter();
        
        filter.type = params.type || 'lowpass';
        filter.frequency.value = params.frequency || 1000;
        filter.Q.value = params.Q || 1;
        filter.gain.value = params.gain || 0;
        
        return filter;
    }
    
    /**
     * 创建失真效果
     * @param {Object} params - 失真参数
     * @returns {WaveShaperNode}
     */
    createDistortion(params = {}) {
        const waveshaper = this.context.createWaveShaper();
        const amount = params.amount || 50;
        const samples = 44100;
        const curve = new Float32Array(samples);
        
        for (let i = 0; i < samples; i++) {
            const x = (i * 2) / samples - 1;
            curve[i] = ((3 + amount) * x * 20 * Math.PI / 180) / (Math.PI + amount * Math.abs(x));
        }
        
        waveshaper.curve = curve;
        waveshaper.oversample = '4x';
        
        return waveshaper;
    }
    
    /**
     * 创建压缩器
     * @param {Object} params - 压缩器参数
     * @returns {DynamicsCompressorNode}
     */
    createCompressor(params = {}) {
        const compressor = this.context.createDynamicsCompressor();
        
        compressor.threshold.value = params.threshold || -24;
        compressor.knee.value = params.knee || 30;
        compressor.ratio.value = params.ratio || 12;
        compressor.attack.value = params.attack || 0.003;
        compressor.release.value = params.release || 0.25;
        
        return compressor;
    }
    
    /**
     * 创建音频分析器
     * @param {number} fftSize - FFT大小
     * @returns {AnalyserNode}
     */
    createAnalyser(fftSize = 2048) {
        const analyser = this.context.createAnalyser();
        analyser.fftSize = fftSize;
        analyser.smoothingTimeConstant = 0.8;
        
        return analyser;
    }
    
    /**
     * 录制音频
     * @param {MediaStream} stream - 媒体流
     * @returns {Object}
     */
    createRecorder(stream) {
        const source = this.context.createMediaStreamSource(stream);
        const processor = this.context.createScriptProcessor(4096, 1, 1);
        const recordedChunks = [];
        
        let isRecording = false;
        
        processor.onaudioprocess = (event) => {
            if (!isRecording) return;
            
            const inputData = event.inputBuffer.getChannelData(0);
            const chunk = new Float32Array(inputData);
            recordedChunks.push(chunk);
        };
        
        source.connect(processor);
        processor.connect(this.context.destination);
        
        return {
            start: () => {
                isRecording = true;
                recordedChunks.length = 0;
            },
            stop: () => {
                isRecording = false;
                return this.exportRecording(recordedChunks);
            },
            source: source
        };
    }
    
    /**
     * 导出录音
     * @param {Array} chunks - 音频块
     * @returns {AudioBuffer}
     */
    exportRecording(chunks) {
        const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
        const audioBuffer = this.context.createBuffer(1, totalLength, this.context.sampleRate);
        const channelData = audioBuffer.getChannelData(0);
        
        let offset = 0;
        chunks.forEach(chunk => {
            channelData.set(chunk, offset);
            offset += chunk.length;
        });
        
        return audioBuffer;
    }
    
    /**
     * 设置主音量
     * @param {number} volume - 音量值 (0-1)
     */
    setMasterVolume(volume) {
        if (this.masterGain) {
            this.masterGain.gain.value = Math.max(0, Math.min(1, volume));
        }
    }
    
    /**
     * 获取当前时间
     * @returns {number}
     */
    getCurrentTime() {
        return this.context ? this.context.currentTime : 0;
    }
    
    /**
     * 暂停音频上下文
     */
    suspend() {
        if (this.context && this.context.state === 'running') {
            return this.context.suspend();
        }
    }
    
    /**
     * 恢复音频上下文
     */
    resume() {
        if (this.context && this.context.state === 'suspended') {
            return this.context.resume();
        }
    }
}

// 使用示例
const audioEngine = new AudioEngine();

// 初始化音频引擎
await audioEngine.initialize();

// 加载音频文件
await audioEngine.loadAudio('music.mp3', 'bgm');
await audioEngine.loadAudio('click.wav', 'click');

// 播放背景音乐
const bgmSource = audioEngine.playAudio('bgm', {
    loop: true,
    volume: 0.7
});

// 播放点击音效
document.getElementById('button').addEventListener('click', () => {
    audioEngine.playAudio('click', {
        volume: 0.5,
        playbackRate: 1.2
    });
});

// 创建音频效果链
const reverb = audioEngine.createEffect('reverb', {
    length: audioEngine.context.sampleRate * 3,
    decay: 2
});

const delay = audioEngine.createEffect('delay', {
    delayTime: 0.3,
    feedback: 0.4,
    wet: 0.3
});

// 连接效果链
bgmSource.disconnect();
bgmSource.connect(reverb);
reverb.connect(delay.input);
delay.output.connect(audioEngine.masterGain);
### 14. 触摸手势接口:移动端交互的基础

#### 🔍 应用场景
移动端手势识别、触摸交互、手势控制、多点触控

#### ❌ 常见问题
```javascript
// ❌ 简单的触摸事件处理,功能有限
element.addEventListener('touchstart', (e) => {
    console.log('触摸开始');
});

✅ 推荐方案

// ✅ 专业的触摸手势管理器
class TouchGestureManager {
    constructor(element, options = {}) {
        this.element = element;
        this.options = {
            enableTap: true,
            enableSwipe: true,
            enablePinch: true,
            enableRotate: true,
            enableLongPress: true,
            tapTimeout: 300,
            longPressTimeout: 500,
            swipeThreshold: 50,
            ...options
        };
        
        this.touches = new Map();
        this.gestureState = {
            isActive: false,
            startTime: 0,
            startDistance: 0,
            startAngle: 0,
            lastScale: 1,
            lastRotation: 0
        };
        
        this.listeners = new Map();
        this.longPressTimer = null;
        this.mouseDown = false;
        
        this.init();
    }
    
    /**
     * 初始化事件监听
     */
    init() {
        // 触摸事件
        this.handleTouchStart = this.handleTouchStart.bind(this);
        this.handleTouchMove = this.handleTouchMove.bind(this);
        this.handleTouchEnd = this.handleTouchEnd.bind(this);
        this.handleTouchCancel = this.handleTouchCancel.bind(this);
        
        this.element.addEventListener('touchstart', this.handleTouchStart, { passive: false });
        this.element.addEventListener('touchmove', this.handleTouchMove, { passive: false });
        this.element.addEventListener('touchend', this.handleTouchEnd, { passive: false });
        this.element.addEventListener('touchcancel', this.handleTouchCancel, { passive: false });
        
        // 鼠标事件(用于桌面端测试)
        this.handleMouseDown = this.handleMouseDown.bind(this);
        this.handleMouseMove = this.handleMouseMove.bind(this);
        this.handleMouseUp = this.handleMouseUp.bind(this);
        
        this.element.addEventListener('mousedown', this.handleMouseDown);
        document.addEventListener('mousemove', this.handleMouseMove);
        document.addEventListener('mouseup', this.handleMouseUp);
    }
    
    /**
     * 处理触摸开始
     * @param {TouchEvent} event - 触摸事件
     */
    handleTouchStart(event) {
        event.preventDefault();
        
        const touches = Array.from(event.touches);
        this.updateTouches(touches);
        
        if (touches.length === 1) {
            // 单点触摸
            const touch = touches[0];
            this.gestureState.startX = touch.clientX;
            this.gestureState.startY = touch.clientY;
            this.gestureState.startTime = Date.now();
            
            // 长按检测
            if (this.options.enableLongPress) {
                this.longPressTimer = setTimeout(() => {
                    this.emit('longpress', {
                        x: touch.clientX,
                        y: touch.clientY,
                        touch: touch
                    });
                }, this.options.longPressTimeout);
            }
        } else if (touches.length === 2) {
            // 双点触摸
            this.gestureState.isActive = true;
            this.gestureState.startDistance = this.getDistance(touches[0], touches[1]);
            this.gestureState.startAngle = this.getAngle(touches[0], touches[1]);
            this.gestureState.lastScale = 1;
            this.gestureState.lastRotation = 0;
            
            // 清除长按定时器
            if (this.longPressTimer) {
                clearTimeout(this.longPressTimer);
                this.longPressTimer = null;
            }
        }
    }
    
    /**
     * 处理触摸移动
     * @param {TouchEvent} event - 触摸事件
     */
    handleTouchMove(event) {
        event.preventDefault();
        
        const touches = Array.from(event.touches);
        this.updateTouches(touches);
        
        if (touches.length === 2 && this.gestureState.isActive) {
            const currentDistance = this.getDistance(touches[0], touches[1]);
            const currentAngle = this.getAngle(touches[0], touches[1]);
            const center = this.getCenter(touches[0], touches[1]);
            
            // 缩放检测
            if (this.options.enablePinch) {
                const scale = currentDistance / this.gestureState.startDistance;
                const deltaScale = scale / this.gestureState.lastScale;
                
                this.emit('pinch', {
                    scale: scale,
                    deltaScale: deltaScale,
                    center: center,
                    touches: touches
                });
                
                this.gestureState.lastScale = scale;
            }
            
            // 旋转检测
            if (this.options.enableRotate) {
                let rotation = currentAngle - this.gestureState.startAngle;
                
                // 处理角度跨越
                if (rotation > 180) rotation -= 360;
                if (rotation < -180) rotation += 360;
                
                const deltaRotation = rotation - this.gestureState.lastRotation;
                
                this.emit('rotate', {
                    rotation: rotation,
                    deltaRotation: deltaRotation,
                    center: center,
                    touches: touches
                });
                
                this.gestureState.lastRotation = rotation;
            }
        }
        
        // 清除长按定时器(移动时取消长按)
        if (this.longPressTimer) {
            clearTimeout(this.longPressTimer);
            this.longPressTimer = null;
        }
    }
    
    /**
     * 处理触摸结束
     * @param {TouchEvent} event - 触摸事件
     */
    handleTouchEnd(event) {
        event.preventDefault();
        
        const changedTouches = Array.from(event.changedTouches);
        const remainingTouches = Array.from(event.touches);
        
        this.updateTouches(remainingTouches);
        
        // 重置手势状态
        if (remainingTouches.length === 0) {
            this.gestureState.isActive = false;
        }
        
        // 单点手势检测
        const duration = Date.now() - this.gestureState.startTime;
        
        if (changedTouches.length === 1 && duration < this.options.tapTimeout) {
            const touch = changedTouches[0];
            const deltaX = Math.abs(touch.clientX - this.gestureState.startX);
            const deltaY = Math.abs(touch.clientY - this.gestureState.startY);
            
            // 点击检测
            if (this.options.enableTap && deltaX < 10 && deltaY < 10) {
                this.emit('tap', {
                    x: touch.clientX,
                    y: touch.clientY,
                    touch: touch
                });
            }
            
            // 滑动检测
            if (this.options.enableSwipe && (deltaX > this.options.swipeThreshold || deltaY > this.options.swipeThreshold)) {
                let direction;
                if (deltaX > deltaY) {
                    direction = touch.clientX > this.gestureState.startX ? 'right' : 'left';
                } else {
                    direction = touch.clientY > this.gestureState.startY ? 'down' : 'up';
                }
                
                this.emit('swipe', {
                    direction: direction,
                    deltaX: touch.clientX - this.gestureState.startX,
                    deltaY: touch.clientY - this.gestureState.startY,
                    velocity: Math.sqrt(deltaX * deltaX + deltaY * deltaY) / duration,
                    touch: touch
                });
            }
        }
        
        // 清除长按定时器
        if (this.longPressTimer) {
            clearTimeout(this.longPressTimer);
            this.longPressTimer = null;
        }
    }
    
    /**
     * 处理触摸取消
     * @param {TouchEvent} event - 触摸事件
     */
    handleTouchCancel(event) {
        this.handleTouchEnd(event);
    }
    
    /**
     * 更新触摸点
     * @param {Array} touches - 触摸点数组
     */
    updateTouches(touches) {
        this.touches.clear();
        touches.forEach(touch => {
            this.touches.set(touch.identifier, {
                x: touch.clientX,
                y: touch.clientY,
                timestamp: Date.now()
            });
        });
    }
    
    /**
     * 获取两点间距离
     * @param {Touch} touch1 - 触摸点1
     * @param {Touch} touch2 - 触摸点2
     * @returns {number}
     */
    getDistance(touch1, touch2) {
        const dx = touch2.clientX - touch1.clientX;
        const dy = touch2.clientY - touch1.clientY;
        return Math.sqrt(dx * dx + dy * dy);
    }
    
    /**
     * 获取两点间角度
     * @param {Touch} touch1 - 触摸点1
     * @param {Touch} touch2 - 触摸点2
     * @returns {number}
     */
    getAngle(touch1, touch2) {
        const dx = touch2.clientX - touch1.clientX;
        const dy = touch2.clientY - touch1.clientY;
        return Math.atan2(dy, dx) * 180 / Math.PI;
    }
    
    /**
     * 获取两点中心
     * @param {Touch} touch1 - 触摸点1
     * @param {Touch} touch2 - 触摸点2
     * @returns {Object}
     */
    getCenter(touch1, touch2) {
        return {
            x: (touch1.clientX + touch2.clientX) / 2,
            y: (touch1.clientY + touch2.clientY) / 2
        };
    }
    
    /**
     * 重置手势状态
     */
    resetGestureState() {
        this.gestureState = {
            isActive: false,
            startTime: 0,
            startDistance: 0,
            startAngle: 0,
            lastScale: 1,
            lastRotation: 0
        };
    }
    
    /**
     * 鼠标事件处理(桌面端测试)
     */
    handleMouseDown(event) {
        this.mouseDown = true;
        this.handleTouchStart({
            preventDefault: () => event.preventDefault(),
            touches: [{
                identifier: 0,
                clientX: event.clientX,
                clientY: event.clientY
            }]
        });
    }
    
    handleMouseMove(event) {
        if (!this.mouseDown) return;
        this.handleTouchMove({
            preventDefault: () => event.preventDefault(),
            touches: [{
                identifier: 0,
                clientX: event.clientX,
                clientY: event.clientY
            }]
        });
    }
    
    handleMouseUp(event) {
        if (!this.mouseDown) return;
        this.mouseDown = false;
        this.handleTouchEnd({
            preventDefault: () => event.preventDefault(),
            touches: [],
            changedTouches: [{
                identifier: 0,
                clientX: event.clientX,
                clientY: event.clientY
            }]
        });
    }
    
    /**
     * 事件监听
     * @param {string} event - 事件名
     * @param {Function} callback - 回调函数
     */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    /**
     * 移除事件监听
     * @param {string} event - 事件名
     * @param {Function} callback - 回调函数
     */
    off(event, callback) {
        const callbacks = this.listeners.get(event) || [];
        const index = callbacks.indexOf(callback);
        if (index > -1) {
            callbacks.splice(index, 1);
        }
    }
    
    /**
     * 触发事件
     * @param {string} event - 事件名
     * @param {*} data - 事件数据
     */
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(callback => callback(data));
    }
    
    /**
     * 销毁手势管理器
     */
    destroy() {
        // 移除事件监听器
        this.element.removeEventListener('touchstart', this.handleTouchStart);
        this.element.removeEventListener('touchmove', this.handleTouchMove);
        this.element.removeEventListener('touchend', this.handleTouchEnd);
        this.element.removeEventListener('touchcancel', this.handleTouchCancel);
        
        this.element.removeEventListener('mousedown', this.handleMouseDown);
        this.element.removeEventListener('mousemove', this.handleMouseMove);
        this.element.removeEventListener('mouseup', this.handleMouseUp);
        
        // 清理定时器
        if (this.longPressTimer) {
            clearTimeout(this.longPressTimer);
        }
        
        // 清理数据
        this.touches.clear();
        this.listeners.clear();
    }
}

// 使用示例
const gestureElement = document.getElementById('gesture-area');
const gestureManager = new TouchGestureManager(gestureElement);

// 监听手势事件
gestureManager.on('tap', (data) => {
    console.log('点击:', data);
});

gestureManager.on('swipe', (data) => {
    console.log('滑动:', data.direction, data.velocity);
});

gestureManager.on('pinch', (data) => {
    console.log('缩放:', data.scale);
    gestureElement.style.transform = `scale(${data.scale})`;
});

gestureManager.on('rotate', (data) => {
    console.log('旋转:', data.rotation);
    gestureElement.style.transform = `rotate(${data.rotation}deg)`;
});

gestureManager.on('longpress', (data) => {
    console.log('长按:', data);
});

15. 地理位置接口:位置服务的核心

🔍 应用场景

地图应用、位置签到、导航服务、基于位置的推荐

❌ 常见问题

// ❌ 简单的位置获取,缺乏错误处理和优化
navigator.geolocation.getCurrentPosition((position) => {
    console.log(position.coords.latitude, position.coords.longitude);
});

✅ 推荐方案

// ✅ 专业的地理位置管理器
class GeolocationManager {
    constructor(options = {}) {
        this.options = {
            enableHighAccuracy: true,
            timeout: 10000,
            maximumAge: 300000, // 5分钟缓存
            ...options
        };
        
        this.currentPosition = null;
        this.watchId = null;
        this.listeners = new Map();
        this.isSupported = 'geolocation' in navigator;
    }
    
    /**
     * 检查地理位置支持
     * @returns {boolean}
     */
    isGeolocationSupported() {
        return this.isSupported;
    }
    
    /**
     * 获取当前位置
     * @param {Object} options - 选项
     * @returns {Promise<Position>}
     */
    async getCurrentPosition(options = {}) {
        if (!this.isSupported) {
            throw new Error('Geolocation is not supported');
        }
        
        const finalOptions = { ...this.options, ...options };
        
        return new Promise((resolve, reject) => {
            navigator.geolocation.getCurrentPosition(
                (position) => {
                    this.currentPosition = position;
                    this.emit('positionUpdate', position);
                    resolve(position);
                },
                (error) => {
                    this.handleError(error);
                    reject(error);
                },
                finalOptions
            );
        });
    }
    
    /**
     * 开始监听位置变化
     * @param {Object} options - 选项
     * @returns {number} watchId
     */
    startWatching(options = {}) {
        if (!this.isSupported) {
            throw new Error('Geolocation is not supported');
        }
        
        if (this.watchId !== null) {
            this.stopWatching();
        }
        
        const finalOptions = { ...this.options, ...options };
        
        this.watchId = navigator.geolocation.watchPosition(
            (position) => {
                this.currentPosition = position;
                this.emit('positionUpdate', position);
            },
            (error) => {
                this.handleError(error);
            },
            finalOptions
        );
        
        return this.watchId;
    }
    
    /**
     * 停止监听位置变化
     */
    stopWatching() {
        if (this.watchId !== null) {
            navigator.geolocation.clearWatch(this.watchId);
            this.watchId = null;
            this.emit('watchStopped');
        }
    }
    
    /**
     * 计算两点间距离
     * @param {Object} pos1 - 位置1
     * @param {Object} pos2 - 位置2
     * @returns {number} 距离(米)
     */
    calculateDistance(pos1, pos2) {
        const R = 6371e3; // 地球半径(米)
        const φ1 = pos1.latitude * Math.PI / 180;
        const φ2 = pos2.latitude * Math.PI / 180;
        const Δφ = (pos2.latitude - pos1.latitude) * Math.PI / 180;
        const Δλ = (pos2.longitude - pos1.longitude) * Math.PI / 180;
        
        const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
                  Math.cos1) * Math.cos2) *
                  Math.sin(Δλ/2) * Math.sin(Δλ/2);
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        
        return R * c;
    }
    
    /**
     * 处理地理位置错误
     * @param {GeolocationPositionError} error - 错误对象
     */
    handleError(error) {
        let message;
        
        switch (error.code) {
            case error.PERMISSION_DENIED:
                message = '用户拒绝了地理位置请求';
                break;
            case error.POSITION_UNAVAILABLE:
                message = '位置信息不可用';
                break;
            case error.TIMEOUT:
                message = '获取位置信息超时';
                break;
            default:
                message = '获取位置信息时发生未知错误';
                break;
        }
        
        this.emit('error', { code: error.code, message });
    }
    
    /**
     * 事件监听
     * @param {string} event - 事件名
     * @param {Function} callback - 回调函数
     */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    /**
     * 触发事件
     * @param {string} event - 事件名
     * @param {*} data - 事件数据
     */
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(callback => callback(data));
    }
}

// 使用示例
const geoManager = new GeolocationManager({
    enableHighAccuracy: true,
    timeout: 15000,
    maximumAge: 600000
});

// 检查支持
if (geoManager.isGeolocationSupported()) {
    // 获取当前位置
    try {
        const position = await geoManager.getCurrentPosition();
        console.log('当前位置:', position.coords);
    } catch (error) {
        console.error('获取位置失败:', error);
    }
    
    // 监听位置变化
    geoManager.on('positionUpdate', (position) => {
        console.log('位置更新:', position.coords);
        updateMap(position.coords);
    });
    
    geoManager.startWatching();
} else {
     console.log('浏览器不支持地理位置');
 }

16. 设备方向接口:感知设备状态

🔍 应用场景

移动端游戏、AR应用、设备姿态检测、重力感应

❌ 常见问题

// ❌ 简单的设备方向监听,缺乏数据处理
window.addEventListener('deviceorientation', (event) => {
    console.log(event.alpha, event.beta, event.gamma);
});

✅ 推荐方案

// ✅ 专业的设备方向管理器
class DeviceOrientationManager {
    constructor(options = {}) {
        this.options = {
            enableSmoothing: true,
            smoothingFactor: 0.8,
            threshold: 1, // 度数阈值
            ...options
        };
        
        this.isSupported = 'DeviceOrientationEvent' in window;
        this.isListening = false;
        this.listeners = new Map();
        
        this.currentOrientation = {
            alpha: 0,   // Z轴旋转
            beta: 0,    // X轴旋转
            gamma: 0,   // Y轴旋转
            absolute: false
        };
        
        this.smoothedOrientation = { ...this.currentOrientation };
        this.lastOrientation = { ...this.currentOrientation };
    }
    
    /**
     * 检查设备方向支持
     * @returns {boolean}
     */
    isDeviceOrientationSupported() {
        return this.isSupported;
    }
    
    /**
     * 请求权限(iOS 13+需要)
     * @returns {Promise<boolean>}
     */
    async requestPermission() {
        if (typeof DeviceOrientationEvent.requestPermission === 'function') {
            try {
                const permission = await DeviceOrientationEvent.requestPermission();
                return permission === 'granted';
            } catch (error) {
                console.error('设备方向权限请求失败:', error);
                return false;
            }
        }
        return true; // 其他平台默认允许
    }
    
    /**
     * 开始监听设备方向
     * @returns {Promise<boolean>}
     */
    async startListening() {
        if (!this.isSupported) {
            throw new Error('Device orientation is not supported');
        }
        
        if (this.isListening) {
            return true;
        }
        
        // 请求权限
        const hasPermission = await this.requestPermission();
        if (!hasPermission) {
            throw new Error('Device orientation permission denied');
        }
        
        // 绑定事件处理器
        this.handleDeviceOrientation = this.handleDeviceOrientation.bind(this);
        window.addEventListener('deviceorientation', this.handleDeviceOrientation);
        
        this.isListening = true;
        this.emit('started');
        
        return true;
    }
    
    /**
     * 停止监听设备方向
     */
    stopListening() {
        if (!this.isListening) return;
        
        window.removeEventListener('deviceorientation', this.handleDeviceOrientation);
        this.isListening = false;
        this.emit('stopped');
    }
    
    /**
     * 处理设备方向事件
     * @param {DeviceOrientationEvent} event - 设备方向事件
     */
    handleDeviceOrientation(event) {
        const newOrientation = {
            alpha: event.alpha || 0,
            beta: event.beta || 0,
            gamma: event.gamma || 0,
            absolute: event.absolute || false
        };
        
        // 数据平滑处理
        if (this.options.enableSmoothing) {
            this.smoothedOrientation = this.smoothOrientation(newOrientation);
        } else {
            this.smoothedOrientation = newOrientation;
        }
        
        // 检查变化阈值
        if (this.hasSignificantChange(this.smoothedOrientation, this.lastOrientation)) {
            this.currentOrientation = { ...this.smoothedOrientation };
            this.lastOrientation = { ...this.smoothedOrientation };
            
            // 触发事件
            this.emit('orientationchange', {
                orientation: this.currentOrientation,
                raw: newOrientation
            });
            
            // 检测特定方向
            this.detectOrientation();
        }
    }
    
    /**
     * 平滑方向数据
     * @param {Object} newOrientation - 新的方向数据
     * @returns {Object}
     */
    smoothOrientation(newOrientation) {
        const factor = this.options.smoothingFactor;
        
        return {
            alpha: this.smoothAngle(this.smoothedOrientation.alpha, newOrientation.alpha, factor),
            beta: this.smoothAngle(this.smoothedOrientation.beta, newOrientation.beta, factor),
            gamma: this.smoothAngle(this.smoothedOrientation.gamma, newOrientation.gamma, factor),
            absolute: newOrientation.absolute
        };
    }
    
    /**
     * 平滑角度值
     * @param {number} oldAngle - 旧角度
     * @param {number} newAngle - 新角度
     * @param {number} factor - 平滑因子
     * @returns {number}
     */
    smoothAngle(oldAngle, newAngle, factor) {
        // 处理角度跨越(0-360度)
        let diff = newAngle - oldAngle;
        if (diff > 180) diff -= 360;
        if (diff < -180) diff += 360;
        
        return oldAngle + diff * (1 - factor);
    }
    
    /**
     * 检查是否有显著变化
     * @param {Object} current - 当前方向
     * @param {Object} last - 上次方向
     * @returns {boolean}
     */
    hasSignificantChange(current, last) {
        const threshold = this.options.threshold;
        
        return Math.abs(current.alpha - last.alpha) > threshold ||
               Math.abs(current.beta - last.beta) > threshold ||
               Math.abs(current.gamma - last.gamma) > threshold;
    }
    
    /**
     * 检测设备方向
     */
    detectOrientation() {
        const { beta, gamma } = this.currentOrientation;
        let orientation = 'unknown';
        
        // 检测设备方向
        if (Math.abs(beta) < 45) {
            if (Math.abs(gamma) < 45) {
                orientation = 'flat';
            } else if (gamma > 45) {
                orientation = 'left';
            } else if (gamma < -45) {
                orientation = 'right';
            }
        } else if (beta > 45) {
            orientation = 'forward';
        } else if (beta < -45) {
            orientation = 'backward';
        }
        
        this.emit('orientationdetected', {
            orientation: orientation,
            angles: this.currentOrientation
        });
    }
    
    /**
     * 获取当前方向
     * @returns {Object}
     */
    getCurrentOrientation() {
        return { ...this.currentOrientation };
    }
    
    /**
     * 计算倾斜角度
     * @returns {Object}
     */
    getTiltAngles() {
        const { beta, gamma } = this.currentOrientation;
        
        return {
            pitch: beta,    // 前后倾斜
            roll: gamma,    // 左右倾斜
            magnitude: Math.sqrt(beta * beta + gamma * gamma)
        };
    }
    
    /**
     * 事件监听
     * @param {string} event - 事件名
     * @param {Function} callback - 回调函数
     */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    /**
     * 触发事件
     * @param {string} event - 事件名
     * @param {*} data - 事件数据
     */
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(callback => callback(data));
    }
    
    /**
     * 销毁管理器
     */
    destroy() {
        this.stopListening();
        this.listeners.clear();
    }
}

// 使用示例
const orientationManager = new DeviceOrientationManager({
    enableSmoothing: true,
    smoothingFactor: 0.8,
    threshold: 2
});

// 检查支持
if (orientationManager.isDeviceOrientationSupported()) {
    // 开始监听
    try {
        await orientationManager.startListening();
        
        // 监听方向变化
        orientationManager.on('orientationchange', (data) => {
            console.log('方向变化:', data.orientation);
            updateUI(data.orientation);
        });
        
        // 监听方向检测
        orientationManager.on('orientationdetected', (data) => {
            console.log('设备方向:', data.orientation);
        });
        
    } catch (error) {
        console.error('启动设备方向监听失败:', error);
    }
} else {
    console.log('设备不支持方向感应');
}

17. 网络信息接口:网络状态感知

🔍 应用场景

网络状态监控、自适应加载、离线处理、性能优化

❌ 常见问题

// ❌ 简单的网络状态检查,信息有限
if (navigator.onLine) {
    console.log('在线');
} else {
    console.log('离线');
}

✅ 推荐方案

// ✅ 专业的网络信息管理器
class NetworkManager {
    constructor(options = {}) {
        this.options = {
            enableConnectionMonitoring: true,
            enableSpeedTest: true,
            speedTestInterval: 30000, // 30秒
            speedTestUrl: '/api/ping',
            ...options
        };
        
        this.listeners = new Map();
        this.connectionInfo = {
            online: navigator.onLine,
            type: 'unknown',
            effectiveType: 'unknown',
            downlink: 0,
            rtt: 0,
            saveData: false
        };
        
        this.speedTestResults = [];
        this.speedTestTimer = null;
        
        this.init();
    }
    
    /**
     * 初始化网络管理器
     */
    init() {
        // 监听在线/离线状态
        window.addEventListener('online', this.handleOnline.bind(this));
        window.addEventListener('offline', this.handleOffline.bind(this));
        
        // 监听网络连接变化
        if ('connection' in navigator) {
            const connection = navigator.connection;
            connection.addEventListener('change', this.handleConnectionChange.bind(this));
            this.updateConnectionInfo();
        }
        
        // 开始速度测试
        if (this.options.enableSpeedTest) {
            this.startSpeedTest();
        }
    }
    
    /**
     * 处理在线事件
     */
    handleOnline() {
        this.connectionInfo.online = true;
        this.emit('online', this.connectionInfo);
        this.emit('statuschange', this.connectionInfo);
        
        // 重新开始速度测试
        if (this.options.enableSpeedTest) {
            this.startSpeedTest();
        }
    }
    
    /**
     * 处理离线事件
     */
    handleOffline() {
        this.connectionInfo.online = false;
        this.emit('offline', this.connectionInfo);
        this.emit('statuschange', this.connectionInfo);
        
        // 停止速度测试
        this.stopSpeedTest();
    }
    
    /**
     * 处理连接变化
     */
    handleConnectionChange() {
        this.updateConnectionInfo();
        this.emit('connectionchange', this.connectionInfo);
        this.emit('statuschange', this.connectionInfo);
    }
    
    /**
     * 更新连接信息
     */
    updateConnectionInfo() {
        if ('connection' in navigator) {
            const connection = navigator.connection;
            
            this.connectionInfo = {
                ...this.connectionInfo,
                type: connection.type || 'unknown',
                effectiveType: connection.effectiveType || 'unknown',
                downlink: connection.downlink || 0,
                rtt: connection.rtt || 0,
                saveData: connection.saveData || false
            };
        }
    }
    
    /**
     * 开始网络速度测试
     */
    startSpeedTest() {
        if (this.speedTestTimer) {
            clearInterval(this.speedTestTimer);
        }
        
        // 立即执行一次
        this.performSpeedTest();
        
        // 定期执行
        this.speedTestTimer = setInterval(() => {
            this.performSpeedTest();
        }, this.options.speedTestInterval);
    }
    
    /**
     * 停止网络速度测试
     */
    stopSpeedTest() {
        if (this.speedTestTimer) {
            clearInterval(this.speedTestTimer);
            this.speedTestTimer = null;
        }
    }
    
    /**
     * 执行网络速度测试
     */
    async performSpeedTest() {
        if (!this.connectionInfo.online) return;
        
        try {
            const startTime = performance.now();
            const response = await fetch(this.options.speedTestUrl + '?t=' + Date.now(), {
                method: 'HEAD',
                cache: 'no-cache'
            });
            const endTime = performance.now();
            
            const latency = endTime - startTime;
            const result = {
                timestamp: Date.now(),
                latency: latency,
                success: response.ok
            };
            
            this.speedTestResults.push(result);
            
            // 保留最近10次结果
            if (this.speedTestResults.length > 10) {
                this.speedTestResults.shift();
            }
            
            this.emit('speedtest', result);
            
        } catch (error) {
            const result = {
                timestamp: Date.now(),
                latency: -1,
                success: false,
                error: error.message
            };
            
            this.speedTestResults.push(result);
            this.emit('speedtest', result);
        }
    }
    
    /**
     * 获取网络质量评估
     * @returns {Object}
     */
    getNetworkQuality() {
        const { effectiveType, downlink, rtt } = this.connectionInfo;
        let quality = 'unknown';
        let score = 0;
        
        // 基于有效连接类型评分
        switch (effectiveType) {
            case 'slow-2g':
                score += 1;
                break;
            case '2g':
                score += 2;
                break;
            case '3g':
                score += 3;
                break;
            case '4g':
                score += 4;
                break;
        }
        
        // 基于下行速度评分
        if (downlink > 10) score += 2;
        else if (downlink > 5) score += 1;
        
        // 基于RTT评分
        if (rtt < 100) score += 2;
        else if (rtt < 300) score += 1;
        
        // 基于速度测试结果评分
        const avgLatency = this.getAverageLatency();
        if (avgLatency > 0) {
            if (avgLatency < 100) score += 2;
            else if (avgLatency < 300) score += 1;
        }
        
        // 确定质量等级
        if (score >= 8) quality = 'excellent';
        else if (score >= 6) quality = 'good';
        else if (score >= 4) quality = 'fair';
        else if (score >= 2) quality = 'poor';
        else quality = 'very-poor';
        
        return {
            quality: quality,
            score: score,
            details: {
                effectiveType: effectiveType,
                downlink: downlink,
                rtt: rtt,
                avgLatency: avgLatency
            }
        };
    }
    
    /**
     * 获取平均延迟
     * @returns {number}
     */
    getAverageLatency() {
        const successfulTests = this.speedTestResults.filter(result => result.success);
        if (successfulTests.length === 0) return -1;
        
        const totalLatency = successfulTests.reduce((sum, result) => sum + result.latency, 0);
        return totalLatency / successfulTests.length;
    }
    
    /**
     * 获取连接信息
     * @returns {Object}
     */
    getConnectionInfo() {
        return { ...this.connectionInfo };
    }
    
    /**
     * 检查是否为慢速连接
     * @returns {boolean}
     */
    isSlowConnection() {
        const { effectiveType, saveData } = this.connectionInfo;
        return saveData || effectiveType === 'slow-2g' || effectiveType === '2g';
    }
    
    /**
     * 检查是否为移动网络
     * @returns {boolean}
     */
    isMobileConnection() {
        const { type } = this.connectionInfo;
        return type === 'cellular';
    }
    
    /**
     * 获取网络建议
     * @returns {Object}
     */
    getNetworkRecommendations() {
        const quality = this.getNetworkQuality();
        const recommendations = [];
        
        if (this.isSlowConnection()) {
            recommendations.push({
                type: 'performance',
                message: '检测到慢速网络,建议减少数据传输'
            });
        }
        
        if (this.isMobileConnection()) {
            recommendations.push({
                type: 'data-usage',
                message: '当前使用移动网络,注意流量消耗'
            });
        }
        
        if (quality.quality === 'poor' || quality.quality === 'very-poor') {
            recommendations.push({
                type: 'quality',
                message: '网络质量较差,建议启用离线模式'
            });
        }
        
        return {
            quality: quality,
            recommendations: recommendations
        };
    }
    
    /**
     * 事件监听
     * @param {string} event - 事件名
     * @param {Function} callback - 回调函数
     */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    /**
     * 触发事件
     * @param {string} event - 事件名
     * @param {*} data - 事件数据
     */
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(callback => callback(data));
    }
    
    /**
     * 销毁网络管理器
     */
    destroy() {
        window.removeEventListener('online', this.handleOnline);
        window.removeEventListener('offline', this.handleOffline);
        
        if ('connection' in navigator) {
            navigator.connection.removeEventListener('change', this.handleConnectionChange);
        }
        
        this.stopSpeedTest();
        this.listeners.clear();
    }
}

// 使用示例
const networkManager = new NetworkManager({
    enableSpeedTest: true,
    speedTestInterval: 30000
});

// 监听网络状态变化
networkManager.on('statuschange', (info) => {
    console.log('网络状态:', info);
    updateNetworkIndicator(info);
});

networkManager.on('speedtest', (result) => {
    console.log('速度测试:', result);
});

// 获取网络建议
const recommendations = networkManager.getNetworkRecommendations();
console.log('网络建议:', recommendations);

18. 辅助功能接口:无障碍访问支持

🔍 应用场景

无障碍访问、屏幕阅读器支持、键盘导航、语音控制

❌ 常见问题

// ❌ 缺乏无障碍支持的组件
function createButton(text) {
    const button = document.createElement('button');
    button.textContent = text;
    return button;
}

✅ 推荐方案

// ✅ 专业的无障碍功能管理器
class AccessibilityManager {
    constructor(options = {}) {
        this.options = {
            enableKeyboardNavigation: true,
            enableScreenReader: true,
            enableHighContrast: false,
            enableFocusManagement: true,
            announceChanges: true,
            ...options
        };
        
        this.focusHistory = [];
        this.announcements = [];
        this.keyboardTrapStack = [];
        this.listeners = new Map();
        
        this.init();
    }
    
    /**
     * 初始化无障碍管理器
     */
    init() {
        // 创建屏幕阅读器公告区域
        this.createAnnouncementRegion();
        
        // 设置键盘导航
        if (this.options.enableKeyboardNavigation) {
            this.setupKeyboardNavigation();
        }
        
        // 设置焦点管理
        if (this.options.enableFocusManagement) {
            this.setupFocusManagement();
        }
        
        // 检测用户偏好
        this.detectUserPreferences();
    }
    
    /**
     * 创建屏幕阅读器公告区域
     */
    createAnnouncementRegion() {
        // 创建实时公告区域
        this.liveRegion = document.createElement('div');
        this.liveRegion.setAttribute('aria-live', 'polite');
        this.liveRegion.setAttribute('aria-atomic', 'true');
        this.liveRegion.className = 'sr-only';
        this.liveRegion.style.cssText = `
            position: absolute !important;
            width: 1px !important;
            height: 1px !important;
            padding: 0 !important;
            margin: -1px !important;
            overflow: hidden !important;
            clip: rect(0, 0, 0, 0) !important;
            white-space: nowrap !important;
            border: 0 !important;
        `;
        
        // 创建紧急公告区域
        this.assertiveRegion = document.createElement('div');
        this.assertiveRegion.setAttribute('aria-live', 'assertive');
        this.assertiveRegion.setAttribute('aria-atomic', 'true');
        this.assertiveRegion.className = 'sr-only';
        this.assertiveRegion.style.cssText = this.liveRegion.style.cssText;
        
        document.body.appendChild(this.liveRegion);
        document.body.appendChild(this.assertiveRegion);
    }
    
    /**
     * 设置键盘导航
     */
    setupKeyboardNavigation() {
        document.addEventListener('keydown', (event) => {
            this.handleKeyboardNavigation(event);
        });
        
        // 添加跳转链接
        this.addSkipLinks();
    }
    
    /**
     * 添加跳转链接
     */
    addSkipLinks() {
        const skipLinks = document.createElement('div');
        skipLinks.className = 'skip-links';
        skipLinks.innerHTML = `
            <a href="#main-content" class="skip-link">跳转到主要内容</a>
            <a href="#navigation" class="skip-link">跳转到导航</a>
        `;
        
        // 样式
        const style = document.createElement('style');
        style.textContent = `
            .skip-links {
                position: absolute;
                top: -40px;
                left: 6px;
                z-index: 1000;
            }
            .skip-link {
                position: absolute;
                top: -40px;
                left: 6px;
                background: #000;
                color: #fff;
                padding: 8px;
                text-decoration: none;
                z-index: 1000;
            }
            .skip-link:focus {
                top: 6px;
            }
        `;
        
        document.head.appendChild(style);
        document.body.insertBefore(skipLinks, document.body.firstChild);
    }
    
    /**
     * 处理键盘导航
     * @param {KeyboardEvent} event - 键盘事件
     */
    handleKeyboardNavigation(event) {
        const { key, ctrlKey, altKey, shiftKey } = event;
        
        // Escape键处理
        if (key === 'Escape') {
            this.handleEscape(event);
        }
        
        // Tab键陷阱处理
        if (key === 'Tab') {
            this.handleTabTrap(event);
        }
        
        // 方向键导航
        if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {
            this.handleArrowNavigation(event);
        }
        
        // 快捷键处理
        if (ctrlKey || altKey) {
            this.handleShortcuts(event);
        }
    }
    
    /**
     * 处理Escape键
     * @param {KeyboardEvent} event - 键盘事件
     */
    handleEscape(event) {
        // 关闭模态框
        const modal = document.querySelector('[role="dialog"]:not([aria-hidden="true"])');
        if (modal) {
            this.closeModal(modal);
            event.preventDefault();
            return;
        }
        
        // 退出焦点陷阱
        if (this.keyboardTrapStack.length > 0) {
            this.exitKeyboardTrap();
            event.preventDefault();
        }
    }
    
    /**
     * 处理Tab键陷阱
     * @param {KeyboardEvent} event - 键盘事件
     */
    handleTabTrap(event) {
        if (this.keyboardTrapStack.length === 0) return;
        
        const currentTrap = this.keyboardTrapStack[this.keyboardTrapStack.length - 1];
        const focusableElements = this.getFocusableElements(currentTrap.container);
        
        if (focusableElements.length === 0) return;
        
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        
        if (event.shiftKey) {
            // Shift + Tab
            if (document.activeElement === firstElement) {
                lastElement.focus();
                event.preventDefault();
            }
        } else {
            // Tab
            if (document.activeElement === lastElement) {
                firstElement.focus();
                event.preventDefault();
            }
        }
    }
    
    /**
     * 处理方向键导航
     * @param {KeyboardEvent} event - 键盘事件
     */
    handleArrowNavigation(event) {
        const activeElement = document.activeElement;
        const role = activeElement.getAttribute('role');
        
        // 处理菜单导航
        if (role === 'menuitem' || activeElement.closest('[role="menu"]')) {
            this.handleMenuNavigation(event);
        }
        
        // 处理表格导航
        if (activeElement.tagName === 'TD' || activeElement.tagName === 'TH') {
            this.handleTableNavigation(event);
        }
        
        // 处理网格导航
        if (role === 'gridcell' || activeElement.closest('[role="grid"]')) {
            this.handleGridNavigation(event);
        }
    }
    
    /**
     * 设置焦点管理
     */
    setupFocusManagement() {
        // 监听焦点变化
        document.addEventListener('focusin', (event) => {
            this.handleFocusIn(event);
        });
        
        document.addEventListener('focusout', (event) => {
            this.handleFocusOut(event);
        });
        
        // 监听DOM变化
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList') {
                    this.handleDOMChanges(mutation);
                }
            });
        });
        
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
    
    /**
     * 处理焦点进入
     * @param {FocusEvent} event - 焦点事件
     */
    handleFocusIn(event) {
        const element = event.target;
        
        // 记录焦点历史
        this.focusHistory.push({
            element: element,
            timestamp: Date.now()
        });
        
        // 限制历史记录长度
        if (this.focusHistory.length > 10) {
            this.focusHistory.shift();
        }
        
        // 触发焦点事件
        this.emit('focuschange', {
            element: element,
            type: 'focusin'
        });
    }
    
    /**
     * 处理焦点离开
     * @param {FocusEvent} event - 焦点事件
     */
    handleFocusOut(event) {
        this.emit('focuschange', {
            element: event.target,
            type: 'focusout'
        });
    }
    
    /**
     * 检测用户偏好
     */
    detectUserPreferences() {
        // 检测减少动画偏好
        if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
            this.setReducedMotion(true);
        }
        
        // 检测高对比度偏好
        if (window.matchMedia('(prefers-contrast: high)').matches) {
            this.setHighContrast(true);
        }
        
        // 检测颜色方案偏好
        if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
            this.setDarkMode(true);
        }
    }
    
    /**
     * 公告消息给屏幕阅读器
     * @param {string} message - 消息内容
     * @param {string} priority - 优先级 ('polite' | 'assertive')
     */
    announce(message, priority = 'polite') {
        const region = priority === 'assertive' ? this.assertiveRegion : this.liveRegion;
        
        // 清空区域
        region.textContent = '';
        
        // 延迟添加消息,确保屏幕阅读器能检测到变化
        setTimeout(() => {
            region.textContent = message;
        }, 100);
        
        // 记录公告
        this.announcements.push({
            message: message,
            priority: priority,
            timestamp: Date.now()
        });
        
        if (this.options.announceChanges) {
            this.emit('announcement', {
                message: message,
                priority: priority
            });
        }
    }
    
    /**
     * 创建键盘陷阱
     * @param {HTMLElement} container - 容器元素
     * @param {Object} options - 选项
     */
    createKeyboardTrap(container, options = {}) {
        const trap = {
            container: container,
            previousFocus: document.activeElement,
            options: {
                returnFocus: true,
                ...options
            }
        };
        
        this.keyboardTrapStack.push(trap);
        
        // 设置初始焦点
        const focusableElements = this.getFocusableElements(container);
        if (focusableElements.length > 0) {
            focusableElements[0].focus();
        }
        
        return trap;
    }
    
    /**
     * 退出键盘陷阱
     */
    exitKeyboardTrap() {
        if (this.keyboardTrapStack.length === 0) return;
        
        const trap = this.keyboardTrapStack.pop();
        
        // 恢复焦点
        if (trap.options.returnFocus && trap.previousFocus) {
            trap.previousFocus.focus();
        }
    }
    
    /**
     * 获取可聚焦元素
     * @param {HTMLElement} container - 容器元素
     * @returns {HTMLElement[]}
     */
    getFocusableElements(container) {
        const selector = [
            'a[href]',
            'button:not([disabled])',
            'input:not([disabled])',
            'select:not([disabled])',
            'textarea:not([disabled])',
            '[tabindex]:not([tabindex="-1"])',
            '[contenteditable="true"]'
        ].join(', ');
        
        return Array.from(container.querySelectorAll(selector))
            .filter(element => {
                return element.offsetWidth > 0 && 
                       element.offsetHeight > 0 && 
                       !element.hasAttribute('aria-hidden');
            });
    }
    
    /**
     * 设置减少动画
     * @param {boolean} enabled - 是否启用
     */
    setReducedMotion(enabled) {
        document.documentElement.classList.toggle('reduce-motion', enabled);
        this.emit('preferencechange', {
            type: 'reduced-motion',
            enabled: enabled
        });
    }
    
    /**
     * 设置高对比度
     * @param {boolean} enabled - 是否启用
     */
    setHighContrast(enabled) {
        document.documentElement.classList.toggle('high-contrast', enabled);
        this.emit('preferencechange', {
            type: 'high-contrast',
            enabled: enabled
        });
    }
    
    /**
     * 设置深色模式
     * @param {boolean} enabled - 是否启用
     */
    setDarkMode(enabled) {
        document.documentElement.classList.toggle('dark-mode', enabled);
        this.emit('preferencechange', {
            type: 'dark-mode',
            enabled: enabled
        });
    }
    
    /**
     * 创建无障碍按钮
     * @param {Object} config - 按钮配置
     * @returns {HTMLElement}
     */
    createAccessibleButton(config) {
        const button = document.createElement('button');
        
        // 基本属性
        button.textContent = config.text;
        button.type = config.type || 'button';
        
        // 无障碍属性
        if (config.ariaLabel) {
            button.setAttribute('aria-label', config.ariaLabel);
        }
        
        if (config.ariaDescribedBy) {
            button.setAttribute('aria-describedby', config.ariaDescribedBy);
        }
        
        if (config.ariaExpanded !== undefined) {
            button.setAttribute('aria-expanded', config.ariaExpanded);
        }
        
        if (config.ariaControls) {
            button.setAttribute('aria-controls', config.ariaControls);
        }
        
        // 事件处理
        if (config.onClick) {
            button.addEventListener('click', config.onClick);
        }
        
        // 键盘事件
        button.addEventListener('keydown', (event) => {
            if (event.key === 'Enter' || event.key === ' ') {
                event.preventDefault();
                button.click();
            }
        });
        
        return button;
    }
    
    /**
     * 事件监听
     * @param {string} event - 事件名
     * @param {Function} callback - 回调函数
     */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
    }
    
    /**
     * 触发事件
     * @param {string} event - 事件名
     * @param {*} data - 事件数据
     */
    emit(event, data) {
        const callbacks = this.listeners.get(event) || [];
        callbacks.forEach(callback => callback(data));
    }
    
    /**
     * 销毁无障碍管理器
     */
    destroy() {
        // 清理DOM元素
        if (this.liveRegion) {
            this.liveRegion.remove();
        }
        if (this.assertiveRegion) {
            this.assertiveRegion.remove();
        }
        
        // 清理事件监听器
        this.listeners.clear();
        
        // 清理键盘陷阱
        while (this.keyboardTrapStack.length > 0) {
            this.exitKeyboardTrap();
        }
    }
}

// 使用示例
const accessibilityManager = new AccessibilityManager({
    enableKeyboardNavigation: true,
    enableScreenReader: true,
    announceChanges: true
});

// 创建无障碍按钮
const button = accessibilityManager.createAccessibleButton({
    text: '打开菜单',
    ariaLabel: '打开主导航菜单',
    ariaExpanded: false,
    ariaControls: 'main-menu',
    onClick: () => {
        // 切换菜单状态
        const isExpanded = button.getAttribute('aria-expanded') === 'true';
        button.setAttribute('aria-expanded', !isExpanded);
        
        // 公告状态变化
        accessibilityManager.announce(
            isExpanded ? '菜单已关闭' : '菜单已打开'
        );
    }
});

// 监听无障碍事件
accessibilityManager.on('announcement', (data) => {
    console.log('屏幕阅读器公告:', data.message);
});

accessibilityManager.on('focuschange', (data) => {
    console.log('焦点变化:', data.element.tagName);
});

🎯 总结与展望

核心要点回顾

通过本篇文章,我们深入探讨了HTML DOM API的高级接口:

  1. Web Worker接口 - 实现多线程处理,提升应用性能
  2. WebRTC接口 - 构建实时通信应用
  3. WebGL接口 - 创建高性能3D图形
  4. 触摸手势接口 - 优化移动端交互体验
  5. 地理位置接口 - 实现位置感知功能
  6. 设备方向接口 - 感知设备状态变化
  7. 网络信息接口 - 智能网络状态管理
  8. 辅助功能接口 - 构建无障碍访问体验

实践建议

  1. 渐进增强 - 始终提供基础功能的降级方案
  2. 性能优化 - 合理使用高级API,避免过度消耗资源
  3. 用户体验 - 关注不同设备和网络环境下的体验
  4. 无障碍访问 - 确保所有用户都能正常使用应用

相关资源


感谢阅读! 如果这篇文章对你有帮助,请点赞、收藏并分享给更多的开发者。让我们一起构建更好的Web应用!

💡 提示:本文涵盖的API接口较多,建议结合实际项目需求选择性学习和应用。记住,技术的价值在于解决实际问题,而不是为了使用而使用。

🏗️ JavaScript类深度解析 - 从构造函数到现代特性的完整指南

🎯 学习目标:深入理解JavaScript类的核心特性,掌握从基础构造函数到最新私有字段的完整知识体系

📊 难度等级:中级-高级
🏷️ 技术标签#JavaScript #ES6+ #类 #面向对象 #现代特性
⏱️ 阅读时间:约12分钟


🌟 引言

在现代JavaScript开发中,你是否遇到过这样的困扰:

  • 类语法混乱:constructor、extends、super这些关键字用法总是搞不清楚?
  • 私有属性困惑:想要实现真正的私有属性,但不知道最新的语法规范?
  • 静态方法迷茫:static关键字的使用场景和最佳实践不够清晰?
  • 现代特性陌生:公有类字段、静态初始化块等新特性不知道如何应用?

今天深度解析JavaScript类的6个核心特性,从传统构造函数到最新的现代语法,让你的面向对象编程更加专业和高效!


💡 核心特性详解

1. 构造函数(Constructor):类的初始化核心

🔍 应用场景

构造函数是类实例化时自动调用的特殊方法,用于初始化对象的属性和状态。

❌ 常见问题

很多开发者在构造函数中犯这些错误:

// ❌ 错误示例:构造函数中的常见问题
class User {
  constructor(name, email) {
    // 问题1:没有参数验证
    this.name = name;
    this.email = email;
    
    // 问题2:在构造函数中执行异步操作
    this.loadUserData();
    
    // 问题3:没有设置默认值
    this.isActive = true;
  }
  
  async loadUserData() {
    // 异步操作不应该在构造函数中执行
    const data = await fetch('/api/user');
    this.userData = await data.json();
  }
}

✅ 推荐方案

正确的构造函数应该同步、简洁、安全:

/**
 * 用户类的正确构造函数实现
 * @description 提供参数验证、默认值设置和类型检查
 */
class User {
  constructor(name, email, options = {}) {
    // ✅ 参数验证
    if (!name || typeof name !== 'string') {
      throw new Error('Name must be a non-empty string');
    }
    
    if (!this.isValidEmail(email)) {
      throw new Error('Invalid email format');
    }
    
    // ✅ 属性初始化
    this.name = name.trim();
    this.email = email.toLowerCase();
    this.isActive = options.isActive ?? true;
    this.role = options.role || 'user';
    this.createdAt = new Date();
    
    // ✅ 调用初始化方法
    this.initializeDefaults();
  }
  
  /**
   * 邮箱格式验证
   * @param {string} email - 邮箱地址
   * @returns {boolean} 是否有效
   */
  isValidEmail = (email) => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  };
  
  /**
   * 初始化默认设置
   * @description 设置用户的默认配置和权限
   */
  initializeDefaults = () => {
    this.permissions = this.role === 'admin' ? ['read', 'write', 'delete'] : ['read'];
    this.settings = {
      theme: 'light',
      notifications: true,
      language: 'zh-CN'
    };
  };
}

💡 核心要点

  • 同步执行:构造函数必须是同步的,不能包含异步操作
  • 参数验证:始终验证传入的参数,提供清晰的错误信息
  • 默认值处理:使用ES6默认参数和空值合并操作符
  • 职责单一:只负责初始化,复杂逻辑应该分离到其他方法

🎯 实际应用

在实际项目中的用户管理系统应用:

// 实际项目中的应用
class UserManager {
  constructor(apiConfig) {
    this.apiUrl = apiConfig.baseUrl;
    this.timeout = apiConfig.timeout || 5000;
    this.users = new Map();
  }
  
  /**
   * 创建新用户(异步操作分离)
   * @param {Object} userData - 用户数据
   * @returns {Promise<User>} 创建的用户实例
   */
  async createUser(userData) {
    // 先创建用户实例
    const user = new User(userData.name, userData.email, userData.options);
    
    // 再执行异步操作
    try {
      const response = await this.saveToDatabase(user);
      user.id = response.id;
      this.users.set(user.id, user);
      return user;
    } catch (error) {
      throw new Error(`Failed to create user: ${error.message}`);
    }
  }
}

2. 继承(extends):类的扩展机制

🔍 应用场景

使用extends关键字创建类的继承关系,实现代码复用和多态性。

❌ 常见问题

继承中经常出现的错误模式:

// ❌ 错误示例:继承中的常见问题
class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    // 问题1:忘记调用super()
    this.breed = breed; // ReferenceError: Must call super constructor
    
    // 问题2:super()调用位置错误
    this.age = 0;
    super(name); // 应该在最前面调用
  }
  
  // 问题3:方法重写时没有考虑父类逻辑
  speak() {
    return "Woof!";
  }
}

✅ 推荐方案

正确的继承实现应该遵循最佳实践:

/**
 * 动物基类
 * @description 定义所有动物的通用属性和方法
 */
class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
    this.isAlive = true;
    this.energy = 100;
  }
  
  /**
   * 动物发声方法(抽象方法)
   * @returns {string} 动物的叫声
   */
  speak() {
    throw new Error('speak() method must be implemented by subclass');
  }
  
  /**
   * 移动方法
   * @param {number} distance - 移动距离
   */
  move(distance) {
    this.energy -= distance * 0.1;
    console.log(`${this.name} moved ${distance} meters`);
  }
  
  /**
   * 休息方法
   * @param {number} hours - 休息时间
   */
  rest(hours) {
    this.energy = Math.min(100, this.energy + hours * 10);
    console.log(`${this.name} rested for ${hours} hours`);
  }
}

/**
 * 狗类 - 继承自Animal
 * @description 实现狗特有的行为和属性
 */
class Dog extends Animal {
  constructor(name, breed, owner) {
    // ✅ 首先调用父类构造函数
    super(name, 'Canine');
    
    // ✅ 然后初始化子类特有属性
    this.breed = breed;
    this.owner = owner;
    this.loyalty = 100;
    this.tricks = [];
  }
  
  /**
   * 实现父类的抽象方法
   * @returns {string} 狗的叫声
   */
  speak() {
    return `${this.name} says: Woof! Woof!`;
  }
  
  /**
   * 狗特有的方法 - 学习技巧
   * @param {string} trick - 要学习的技巧
   */
  learnTrick(trick) {
    if (!this.tricks.includes(trick)) {
      this.tricks.push(trick);
      console.log(`${this.name} learned a new trick: ${trick}`);
    }
  }
  
  /**
   * 重写父类方法,添加狗特有的行为
   * @param {number} distance - 移动距离
   */
  move(distance) {
    // ✅ 调用父类方法
    super.move(distance);
    
    // ✅ 添加子类特有逻辑
    if (distance > 100) {
      this.loyalty += 5; // 长距离移动增加忠诚度
      console.log(`${this.name}'s loyalty increased!`);
    }
  }
}

/**
 * 工作犬类 - 多层继承
 * @description 具有特殊工作能力的狗
 */
class WorkingDog extends Dog {
  constructor(name, breed, owner, jobType) {
    super(name, breed, owner);
    this.jobType = jobType;
    this.workExperience = 0;
    this.isOnDuty = false;
  }
  
  /**
   * 开始工作
   * @param {number} hours - 工作时间
   */
  startWork(hours) {
    if (this.energy < 30) {
      console.log(`${this.name} is too tired to work`);
      return;
    }
    
    this.isOnDuty = true;
    this.workExperience += hours;
    this.energy -= hours * 5;
    console.log(`${this.name} worked as ${this.jobType} for ${hours} hours`);
  }
  
  /**
   * 重写speak方法,工作犬有不同的叫声
   * @returns {string} 工作犬的叫声
   */
  speak() {
    const baseSound = super.speak();
    return this.isOnDuty ? 
      `${baseSound} (Alert and focused)` : 
      baseSound;
  }
}

💡 核心要点

  • super()调用:子类构造函数必须首先调用super()
  • 方法重写:可以完全重写或扩展父类方法
  • 多层继承:支持多层继承链,但要注意复杂度
  • 抽象方法:父类可以定义需要子类实现的抽象方法

🎯 实际应用

在UI组件库中的继承应用:

// 实际项目中的组件继承
class BaseComponent {
  constructor(element, options = {}) {
    this.element = element;
    this.options = { ...this.defaultOptions, ...options };
    this.isInitialized = false;
    this.eventListeners = new Map();
  }
  
  get defaultOptions() {
    return {
      className: 'base-component',
      disabled: false
    };
  }
  
  init() {
    this.render();
    this.bindEvents();
    this.isInitialized = true;
  }
  
  render() {
    throw new Error('render() must be implemented by subclass');
  }
  
  destroy() {
    this.eventListeners.forEach((listener, event) => {
      this.element.removeEventListener(event, listener);
    });
    this.eventListeners.clear();
  }
}

class Button extends BaseComponent {
  get defaultOptions() {
    return {
      ...super.defaultOptions,
      className: 'btn',
      type: 'button',
      text: 'Click me'
    };
  }
  
  render() {
    this.element.className = this.options.className;
    this.element.textContent = this.options.text;
    this.element.type = this.options.type;
    this.element.disabled = this.options.disabled;
  }
  
  bindEvents() {
    const clickHandler = (e) => this.handleClick(e);
    this.element.addEventListener('click', clickHandler);
    this.eventListeners.set('click', clickHandler);
  }
  
  handleClick(event) {
    if (this.options.disabled) return;
    
    this.element.classList.add('btn-clicked');
    setTimeout(() => {
      this.element.classList.remove('btn-clicked');
    }, 150);
    
    // 触发自定义事件
    this.element.dispatchEvent(new CustomEvent('button:click', {
      detail: { originalEvent: event }
    }));
  }
}

3. 私有元素(Private Fields):真正的封装

🔍 应用场景

使用#语法创建真正私有的属性和方法,实现数据封装和信息隐藏。

❌ 常见问题

传统的"私有"属性实现方式存在问题:

// ❌ 错误示例:伪私有属性
class BankAccount {
  constructor(accountNumber, initialBalance) {
    // 问题1:下划线约定不是真正的私有
    this._accountNumber = accountNumber;
    this._balance = initialBalance;
    
    // 问题2:Symbol虽然更安全,但仍可访问
    this[Symbol.for('secret')] = 'sensitive data';
  }
  
  // 问题3:WeakMap方式过于复杂
  getBalance() {
    return this._balance; // 外部仍可直接访问 instance._balance
  }
}

// 外部可以直接访问"私有"属性
const account = new BankAccount('123456', 1000);
console.log(account._balance); // 1000 - 不应该被访问
account._balance = 999999; // 可以被恶意修改

✅ 推荐方案

使用现代JavaScript的真正私有字段:

/**
 * 银行账户类 - 使用真正的私有字段
 * @description 实现安全的账户管理,保护敏感数据
 */
class BankAccount {
  // ✅ 私有字段声明
  #accountNumber;
  #balance;
  #pin;
  #transactionHistory;
  #isLocked;
  
  // ✅ 私有静态字段
  static #bankCode = 'BANK001';
  static #encryptionKey = 'secret-key-2023';
  
  constructor(accountNumber, initialBalance, pin) {
    // 参数验证
    if (!accountNumber || initialBalance < 0 || !pin) {
      throw new Error('Invalid account parameters');
    }
    
    // ✅ 初始化私有字段
    this.#accountNumber = accountNumber;
    this.#balance = initialBalance;
    this.#pin = this.#hashPin(pin);
    this.#transactionHistory = [];
    this.#isLocked = false;
    
    // 公有属性
    this.accountType = 'savings';
    this.createdAt = new Date();
  }
  
  /**
   * 私有方法 - PIN哈希处理
   * @param {string} pin - 原始PIN
   * @returns {string} 哈希后的PIN
   */
  #hashPin(pin) {
    // 简化的哈希实现(实际项目中应使用专业的加密库)
    return btoa(pin + BankAccount.#encryptionKey);
  }
  
  /**
   * 私有方法 - 验证PIN
   * @param {string} inputPin - 输入的PIN
   * @returns {boolean} 验证结果
   */
  #verifyPin(inputPin) {
    return this.#hashPin(inputPin) === this.#pin;
  }
  
  /**
   * 私有方法 - 记录交易
   * @param {string} type - 交易类型
   * @param {number} amount - 交易金额
   * @param {string} description - 交易描述
   */
  #recordTransaction(type, amount, description) {
    this.#transactionHistory.push({
      id: Date.now(),
      type,
      amount,
      description,
      timestamp: new Date(),
      balance: this.#balance
    });
    
    // 只保留最近100条记录
    if (this.#transactionHistory.length > 100) {
      this.#transactionHistory = this.#transactionHistory.slice(-100);
    }
  }
  
  /**
   * 获取账户余额
   * @param {string} pin - PIN码
   * @returns {number} 账户余额
   */
  getBalance(pin) {
    if (this.#isLocked) {
      throw new Error('Account is locked');
    }
    
    if (!this.#verifyPin(pin)) {
      this.#lockAccount();
      throw new Error('Invalid PIN');
    }
    
    return this.#balance;
  }
  
  /**
   * 存款操作
   * @param {number} amount - 存款金额
   * @param {string} pin - PIN码
   * @param {string} description - 交易描述
   */
  deposit(amount, pin, description = 'Deposit') {
    if (this.#isLocked || !this.#verifyPin(pin) || amount <= 0) {
      throw new Error('Invalid deposit operation');
    }
    
    this.#balance += amount;
    this.#recordTransaction('deposit', amount, description);
    
    console.log(`Deposited $${amount}. New balance: $${this.#balance}`);
  }
  
  /**
   * 取款操作
   * @param {number} amount - 取款金额
   * @param {string} pin - PIN码
   * @param {string} description - 交易描述
   */
  withdraw(amount, pin, description = 'Withdrawal') {
    if (this.#isLocked || !this.#verifyPin(pin)) {
      throw new Error('Invalid withdrawal operation');
    }
    
    if (amount > this.#balance) {
      throw new Error('Insufficient funds');
    }
    
    this.#balance -= amount;
    this.#recordTransaction('withdrawal', amount, description);
    
    console.log(`Withdrew $${amount}. New balance: $${this.#balance}`);
  }
  
  /**
   * 私有方法 - 锁定账户
   */
  #lockAccount() {
    this.#isLocked = true;
    console.log('Account has been locked due to security concerns');
  }
  
  /**
   * 获取交易历史(只返回安全信息)
   * @param {string} pin - PIN码
   * @returns {Array} 交易历史
   */
  getTransactionHistory(pin) {
    if (!this.#verifyPin(pin)) {
      throw new Error('Invalid PIN');
    }
    
    // 返回脱敏的交易历史
    return this.#transactionHistory.map(transaction => ({
      id: transaction.id,
      type: transaction.type,
      amount: transaction.amount,
      description: transaction.description,
      timestamp: transaction.timestamp
      // 注意:不返回余额信息
    }));
  }
  
  /**
   * 获取账户摘要信息
   * @returns {Object} 公开的账户信息
   */
  getAccountSummary() {
    return {
      accountType: this.accountType,
      createdAt: this.createdAt,
      isLocked: this.#isLocked,
      // 账户号码脱敏显示
      accountNumber: this.#accountNumber.replace(/\d(?=\d{4})/g, '*')
    };
  }
}

💡 核心要点

  • 真正私有:#字段无法从类外部访问,提供真正的封装
  • 编译时检查:访问不存在的私有字段会在编译时报错
  • 继承限制:私有字段不能被子类访问,确保封装性
  • 性能优化:私有字段访问比WeakMap方式更高效

🎯 实际应用

在状态管理库中的私有字段应用:

// 实际项目中的状态管理应用
class StateManager {
  // 私有状态存储
  #state;
  #subscribers;
  #middleware;
  #isDispatching;
  
  constructor(initialState = {}) {
    this.#state = { ...initialState };
    this.#subscribers = new Set();
    this.#middleware = [];
    this.#isDispatching = false;
  }
  
  /**
   * 私有方法 - 通知订阅者
   */
  #notifySubscribers() {
    this.#subscribers.forEach(callback => {
      try {
        callback(this.#state);
      } catch (error) {
        console.error('Subscriber error:', error);
      }
    });
  }
  
  /**
   * 私有方法 - 应用中间件
   */
  #applyMiddleware(action) {
    return this.#middleware.reduce((acc, middleware) => {
      return middleware(acc);
    }, action);
  }
  
  /**
   * 获取当前状态(只读)
   * @returns {Object} 状态的深拷贝
   */
  getState() {
    return JSON.parse(JSON.stringify(this.#state));
  }
  
  /**
   * 分发动作
   * @param {Object} action - 要分发的动作
   */
  dispatch(action) {
    if (this.#isDispatching) {
      throw new Error('Cannot dispatch while dispatching');
    }
    
    try {
      this.#isDispatching = true;
      const processedAction = this.#applyMiddleware(action);
      
      // 应用状态变更
      this.#state = this.#reducer(this.#state, processedAction);
      
      // 通知订阅者
      this.#notifySubscribers();
    } finally {
      this.#isDispatching = false;
    }
  }
  
  /**
   * 私有reducer方法
   * @param {Object} state - 当前状态
   * @param {Object} action - 动作
   * @returns {Object} 新状态
   */
  #reducer(state, action) {
    // 简化的reducer实现
    switch (action.type) {
      case 'SET_VALUE':
        return { ...state, [action.key]: action.value };
      case 'RESET':
        return {};
      default:
        return state;
    }
  }
  
  /**
   * 订阅状态变化
   * @param {Function} callback - 回调函数
   * @returns {Function} 取消订阅函数
   */
  subscribe(callback) {
    this.#subscribers.add(callback);
    
    // 返回取消订阅函数
    return () => {
      this.#subscribers.delete(callback);
    };
  }
}

4. 公有类字段(Public Class Fields):简化的属性声明

🔍 应用场景

直接在类体中声明公有属性,无需在构造函数中初始化,提供更清晰的类结构。

❌ 常见问题

传统的属性声明方式存在一些不便:

// ❌ 传统方式:所有属性都在构造函数中声明
class GamePlayer {
  constructor(name) {
    // 问题1:属性声明分散,不易查看类结构
    this.name = name;
    this.level = 1;
    this.health = 100;
    this.mana = 50;
    this.inventory = [];
    this.skills = new Map();
    this.isOnline = false;
    this.lastLoginTime = null;
    
    // 问题2:默认值和初始化逻辑混在一起
    this.stats = {
      strength: 10,
      agility: 10,
      intelligence: 10
    };
    
    // 问题3:方法绑定需要手动处理
    this.handleLevelUp = this.handleLevelUp.bind(this);
  }
  
  handleLevelUp() {
    this.level++;
  }
}

✅ 推荐方案

使用公有类字段简化属性声明:

/**
 * 游戏玩家类 - 使用公有类字段
 * @description 展示现代JavaScript类字段的最佳实践
 */
class GamePlayer {
  // ✅ 公有类字段声明 - 基础属性
  level = 1;
  health = 100;
  maxHealth = 100;
  mana = 50;
  maxMana = 50;
  experience = 0;
  
  // ✅ 复杂类型的默认值
  inventory = [];
  skills = new Map();
  achievements = new Set();
  
  // ✅ 对象类型的默认值
  stats = {
    strength: 10,
    agility: 10,
    intelligence: 10,
    vitality: 10
  };
  
  // ✅ 状态相关字段
  isOnline = false;
  isInCombat = false;
  lastLoginTime = null;
  currentLocation = 'starting_town';
  
  // ✅ 配置相关字段
  settings = {
    soundEnabled: true,
    musicVolume: 0.8,
    effectsVolume: 0.6,
    autoSave: true
  };
  
  // ✅ 箭头函数方法自动绑定this
  handleLevelUp = () => {
    const oldLevel = this.level;
    this.level++;
    this.experience = 0;
    
    // 升级时提升属性
    this.maxHealth += 10;
    this.maxMana += 5;
    this.health = this.maxHealth; // 升级时恢复满血
    this.mana = this.maxMana;
    
    // 每5级获得属性点
    if (this.level % 5 === 0) {
      this.stats.strength += 2;
      this.stats.agility += 2;
      this.stats.intelligence += 2;
      this.stats.vitality += 2;
    }
    
    console.log(`Level up! ${oldLevel}${this.level}`);
    this.onLevelUp?.(this.level, oldLevel);
  };
  
  // ✅ 经验值计算方法
  gainExperience = (amount) => {
    this.experience += amount;
    const requiredExp = this.getRequiredExperience();
    
    if (this.experience >= requiredExp) {
      this.handleLevelUp();
    }
    
    console.log(`Gained ${amount} XP. Progress: ${this.experience}/${requiredExp}`);
  };
  
  // ✅ 技能学习方法
  learnSkill = (skillName, skillData) => {
    if (this.level < skillData.requiredLevel) {
      throw new Error(`Level ${skillData.requiredLevel} required to learn ${skillName}`);
    }
    
    this.skills.set(skillName, {
      ...skillData,
      learnedAt: new Date(),
      level: 1,
      experience: 0
    });
    
    console.log(`Learned new skill: ${skillName}`);
  };
  
  constructor(name, playerClass = 'warrior') {
    // ✅ 构造函数只处理必需的参数
    if (!name || typeof name !== 'string') {
      throw new Error('Player name is required');
    }
    
    this.name = name.trim();
    this.playerClass = playerClass;
    this.id = this.generatePlayerId();
    this.createdAt = new Date();
    
    // ✅ 根据职业调整初始属性
    this.applyClassModifiers(playerClass);
    
    // ✅ 初始化基础技能
    this.initializeClassSkills(playerClass);
  }
  
  /**
   * 生成唯一玩家ID
   * @returns {string} 玩家ID
   */
  generatePlayerId() {
    return `player_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
  
  /**
   * 应用职业修正
   * @param {string} playerClass - 玩家职业
   */
  applyClassModifiers(playerClass) {
    const classModifiers = {
      warrior: { strength: +5, vitality: +3, health: +20 },
      mage: { intelligence: +5, agility: +2, mana: +20 },
      rogue: { agility: +5, strength: +2, health: +10 },
      cleric: { intelligence: +3, vitality: +3, mana: +15, health: +15 }
    };
    
    const modifiers = classModifiers[playerClass] || {};
    
    // 应用属性修正
    Object.entries(modifiers).forEach(([stat, bonus]) => {
      if (stat === 'health') {
        this.maxHealth += bonus;
        this.health = this.maxHealth;
      } else if (stat === 'mana') {
        this.maxMana += bonus;
        this.mana = this.maxMana;
      } else if (this.stats[stat] !== undefined) {
        this.stats[stat] += bonus;
      }
    });
  }
  
  /**
   * 初始化职业技能
   * @param {string} playerClass - 玩家职业
   */
  initializeClassSkills(playerClass) {
    const classSkills = {
      warrior: [
        { name: 'Slash', damage: 15, manaCost: 0, requiredLevel: 1 },
        { name: 'Shield Block', defense: 10, manaCost: 5, requiredLevel: 1 }
      ],
      mage: [
        { name: 'Fireball', damage: 20, manaCost: 10, requiredLevel: 1 },
        { name: 'Heal', healing: 15, manaCost: 8, requiredLevel: 1 }
      ],
      rogue: [
        { name: 'Backstab', damage: 18, manaCost: 5, requiredLevel: 1 },
        { name: 'Stealth', duration: 10, manaCost: 15, requiredLevel: 1 }
      ],
      cleric: [
        { name: 'Heal', healing: 25, manaCost: 10, requiredLevel: 1 },
        { name: 'Bless', buff: 'protection', manaCost: 12, requiredLevel: 1 }
      ]
    };
    
    const skills = classSkills[playerClass] || [];
    skills.forEach(skill => {
      this.skills.set(skill.name, { ...skill, level: 1, experience: 0 });
    });
  }
  
  /**
   * 计算升级所需经验
   * @returns {number} 所需经验值
   */
  getRequiredExperience() {
    return Math.floor(100 * Math.pow(1.5, this.level - 1));
  }
  
  /**
   * 获取玩家状态摘要
   * @returns {Object} 玩家状态
   */
  getStatus() {
    return {
      name: this.name,
      class: this.playerClass,
      level: this.level,
      health: `${this.health}/${this.maxHealth}`,
      mana: `${this.mana}/${this.maxMana}`,
      experience: `${this.experience}/${this.getRequiredExperience()}`,
      location: this.currentLocation,
      skillCount: this.skills.size,
      achievementCount: this.achievements.size
    };
  }
}

💡 核心要点

  • 清晰结构:类字段声明让类的结构一目了然
  • 自动绑定:箭头函数方法自动绑定this上下文
  • 默认值:可以直接为字段设置默认值
  • 类型明确:字段声明使得属性类型更加明确

🎯 实际应用

在React组件中的类字段应用:

// 实际项目中的React组件应用
class TodoComponent extends React.Component {
  // ✅ 状态字段声明
  state = {
    todos: [],
    filter: 'all',
    isLoading: false,
    error: null
  };
  
  // ✅ 配置字段
  maxTodos = 100;
  autoSaveInterval = 5000;
  
  // ✅ 引用字段
  inputRef = React.createRef();
  containerRef = React.createRef();
  
  // ✅ 方法自动绑定
  handleAddTodo = (text) => {
    if (this.state.todos.length >= this.maxTodos) {
      this.setState({ error: 'Maximum todos reached' });
      return;
    }
    
    const newTodo = {
      id: Date.now(),
      text: text.trim(),
      completed: false,
      createdAt: new Date()
    };
    
    this.setState(prevState => ({
      todos: [...prevState.todos, newTodo],
      error: null
    }));
  };
  
  handleToggleTodo = (id) => {
    this.setState(prevState => ({
      todos: prevState.todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    }));
  };
  
  handleDeleteTodo = (id) => {
    this.setState(prevState => ({
      todos: prevState.todos.filter(todo => todo.id !== id)
    }));
  };
  
  componentDidMount() {
    this.loadTodos();
    this.startAutoSave();
  }
  
  componentWillUnmount() {
    this.stopAutoSave();
  }
  
  loadTodos = async () => {
    this.setState({ isLoading: true });
    try {
      const todos = await this.fetchTodos();
      this.setState({ todos, isLoading: false });
    } catch (error) {
      this.setState({ error: error.message, isLoading: false });
    }
  };
  
  startAutoSave = () => {
    this.autoSaveTimer = setInterval(() => {
      this.saveTodos();
    }, this.autoSaveInterval);
  };
  
  stopAutoSave = () => {
    if (this.autoSaveTimer) {
      clearInterval(this.autoSaveTimer);
    }
  };
}

5. 静态方法和属性(Static):类级别的功能

🔍 应用场景

使用static关键字创建属于类本身而非实例的方法和属性,适用于工具函数、常量定义和工厂方法。

❌ 常见问题

静态方法使用中的常见误区:

// ❌ 错误示例:静态方法的误用
class MathUtils {
  constructor() {
    // 问题1:在静态工具类中使用构造函数
    this.precision = 2;
  }
  
  // 问题2:应该是静态的方法却定义为实例方法
  calculateDistance(x1, y1, x2, y2) {
    return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
  }
  
  // 问题3:静态方法中访问实例属性
  static formatNumber(num) {
    return num.toFixed(this.precision); // TypeError: Cannot read property 'precision'
  }
}

// 使用时需要创建实例,不合理
const utils = new MathUtils();
const distance = utils.calculateDistance(0, 0, 3, 4);

✅ 推荐方案

正确使用静态方法和属性:

/**
 * 数学工具类 - 正确的静态方法实现
 * @description 提供各种数学计算的静态工具方法
 */
class MathUtils {
  // ✅ 静态常量
  static PI = Math.PI;
  static E = Math.E;
  static GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
  
  // ✅ 静态配置
  static DEFAULT_PRECISION = 2;
  static MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
  
  // ✅ 私有静态字段
  static #conversionFactors = {
    degrees: Math.PI / 180,
    radians: 180 / Math.PI
  };
  
  /**
   * 计算两点间距离
   * @param {number} x1 - 第一个点的x坐标
   * @param {number} y1 - 第一个点的y坐标
   * @param {number} x2 - 第二个点的x坐标
   * @param {number} y2 - 第二个点的y坐标
   * @returns {number} 距离值
   */
  static calculateDistance(x1, y1, x2, y2) {
    const dx = x2 - x1;
    const dy = y2 - y1;
    return Math.sqrt(dx * dx + dy * dy);
  }
  
  /**
   * 格式化数字
   * @param {number} num - 要格式化的数字
   * @param {number} precision - 精度,默认使用类的默认精度
   * @returns {string} 格式化后的数字字符串
   */
  static formatNumber(num, precision = MathUtils.DEFAULT_PRECISION) {
    if (typeof num !== 'number' || isNaN(num)) {
      throw new Error('Invalid number provided');
    }
    return num.toFixed(precision);
  }
  
  /**
   * 角度转弧度
   * @param {number} degrees - 角度值
   * @returns {number} 弧度值
   */
  static degreesToRadians(degrees) {
    return degrees * MathUtils.#conversionFactors.degrees;
  }
  
  /**
   * 弧度转角度
   * @param {number} radians - 弧度值
   * @returns {number} 角度值
   */
  static radiansToDegrees(radians) {
    return radians * MathUtils.#conversionFactors.radians;
  }
  
  /**
   * 计算斐波那契数列
   * @param {number} n - 序列位置
   * @returns {number} 斐波那契数
   */
  static fibonacci(n) {
    if (n < 0) throw new Error('n must be non-negative');
    if (n <= 1) return n;
    
    let a = 0, b = 1;
    for (let i = 2; i <= n; i++) {
      [a, b] = [b, a + b];
    }
    return b;
  }
  
  /**
   * 检查是否为质数
   * @param {number} num - 要检查的数字
   * @returns {boolean} 是否为质数
   */
  static isPrime(num) {
    if (num < 2) return false;
    if (num === 2) return true;
    if (num % 2 === 0) return false;
    
    for (let i = 3; i <= Math.sqrt(num); i += 2) {
      if (num % i === 0) return false;
    }
    return true;
  }
  
  /**
   * 生成指定范围内的随机整数
   * @param {number} min - 最小值(包含)
   * @param {number} max - 最大值(包含)
   * @returns {number} 随机整数
   */
  static randomInt(min, max) {
    if (min > max) [min, max] = [max, min];
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
  
  /**
   * 计算数组的统计信息
   * @param {number[]} numbers - 数字数组
   * @returns {Object} 统计信息对象
   */
  static getStatistics(numbers) {
    if (!Array.isArray(numbers) || numbers.length === 0) {
      throw new Error('Invalid or empty array provided');
    }
    
    const sorted = [...numbers].sort((a, b) => a - b);
    const sum = numbers.reduce((acc, num) => acc + num, 0);
    const mean = sum / numbers.length;
    
    return {
      count: numbers.length,
      sum,
      mean,
      median: MathUtils.#calculateMedian(sorted),
      mode: MathUtils.#calculateMode(numbers),
      min: sorted[0],
      max: sorted[sorted.length - 1],
      range: sorted[sorted.length - 1] - sorted[0],
      variance: MathUtils.#calculateVariance(numbers, mean),
      standardDeviation: Math.sqrt(MathUtils.#calculateVariance(numbers, mean))
    };
  }
  
  /**
   * 私有静态方法 - 计算中位数
   * @param {number[]} sortedNumbers - 已排序的数字数组
   * @returns {number} 中位数
   */
  static #calculateMedian(sortedNumbers) {
    const mid = Math.floor(sortedNumbers.length / 2);
    return sortedNumbers.length % 2 === 0
      ? (sortedNumbers[mid - 1] + sortedNumbers[mid]) / 2
      : sortedNumbers[mid];
  }
  
  /**
   * 私有静态方法 - 计算众数
   * @param {number[]} numbers - 数字数组
   * @returns {number[]} 众数数组
   */
  static #calculateMode(numbers) {
    const frequency = new Map();
    let maxFreq = 0;
    
    numbers.forEach(num => {
      const freq = (frequency.get(num) || 0) + 1;
      frequency.set(num, freq);
      maxFreq = Math.max(maxFreq, freq);
    });
    
    return [...frequency.entries()]
      .filter(([, freq]) => freq === maxFreq)
      .map(([num]) => num);
  }
  
  /**
   * 私有静态方法 - 计算方差
   * @param {number[]} numbers - 数字数组
   * @param {number} mean - 平均值
   * @returns {number} 方差
   */
  static #calculateVariance(numbers, mean) {
    const squaredDiffs = numbers.map(num => (num - mean) ** 2);
    return squaredDiffs.reduce((acc, diff) => acc + diff, 0) / numbers.length;
  }
  
  // ✅ 防止实例化工具类
  constructor() {
    throw new Error('MathUtils is a static class and cannot be instantiated');
  }
}

/**
 * 用户工厂类 - 展示静态工厂方法模式
 * @description 使用静态方法创建不同类型的用户实例
 */
class UserFactory {
  // ✅ 静态配置
  static DEFAULT_ROLES = ['user', 'admin', 'moderator'];
  static PASSWORD_MIN_LENGTH = 8;
  
  // ✅ 静态计数器
  static #userCount = 0;
  static #adminCount = 0;
  
  /**
   * 创建普通用户
   * @param {string} name - 用户名
   * @param {string} email - 邮箱
   * @param {Object} options - 可选配置
   * @returns {User} 用户实例
   */
  static createUser(name, email, options = {}) {
    const user = new User(name, email, {
      ...options,
      role: 'user',
      id: UserFactory.#generateUserId()
    });
    
    UserFactory.#userCount++;
    return user;
  }
  
  /**
   * 创建管理员用户
   * @param {string} name - 用户名
   * @param {string} email - 邮箱
   * @param {Array} permissions - 权限列表
   * @returns {User} 管理员用户实例
   */
  static createAdmin(name, email, permissions = []) {
    const admin = new User(name, email, {
      role: 'admin',
      permissions: ['read', 'write', 'delete', ...permissions],
      id: UserFactory.#generateAdminId()
    });
    
    UserFactory.#adminCount++;
    return admin;
  }
  
  /**
   * 从JSON数据创建用户
   * @param {Object} userData - 用户数据
   * @returns {User} 用户实例
   */
  static fromJSON(userData) {
    const { name, email, role, ...options } = userData;
    
    switch (role) {
      case 'admin':
        return UserFactory.createAdmin(name, email, options.permissions);
      case 'user':
      default:
        return UserFactory.createUser(name, email, options);
    }
  }
  
  /**
   * 批量创建用户
   * @param {Array} usersData - 用户数据数组
   * @returns {Array} 用户实例数组
   */
  static createBatch(usersData) {
    return usersData.map(userData => UserFactory.fromJSON(userData));
  }
  
  /**
   * 获取用户统计信息
   * @returns {Object} 统计信息
   */
  static getStatistics() {
    return {
      totalUsers: UserFactory.#userCount,
      totalAdmins: UserFactory.#adminCount,
      totalCount: UserFactory.#userCount + UserFactory.#adminCount
    };
  }
  
  /**
   * 私有静态方法 - 生成用户ID
   * @returns {string} 用户ID
   */
  static #generateUserId() {
    return `user_${Date.now()}_${UserFactory.#userCount + 1}`;
  }
  
  /**
   * 私有静态方法 - 生成管理员ID
   * @returns {string} 管理员ID
   */
  static #generateAdminId() {
    return `admin_${Date.now()}_${UserFactory.#adminCount + 1}`;
  }
  
  /**
   * 验证用户数据
   * @param {Object} userData - 用户数据
   * @returns {boolean} 验证结果
   */
  static validateUserData(userData) {
    const { name, email, password } = userData;
    
    if (!name || typeof name !== 'string' || name.trim().length === 0) {
      return false;
    }
    
    if (!email || !UserFactory.#isValidEmail(email)) {
      return false;
    }
    
    if (password && password.length < UserFactory.PASSWORD_MIN_LENGTH) {
      return false;
    }
    
    return true;
  }
  
  /**
   * 私有静态方法 - 邮箱验证
   * @param {string} email - 邮箱地址
   * @returns {boolean} 是否有效
   */
  static #isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

💡 核心要点

  • 类级别访问:静态成员属于类本身,不需要实例化
  • 工具函数:适合创建不依赖实例状态的工具方法
  • 工厂模式:静态方法常用于实现工厂模式
  • 私有静态:可以结合私有字段创建真正私有的静态成员

🎯 实际应用

在配置管理中的静态方法应用:

// 实际项目中的配置管理应用
class ConfigManager {
  // 静态配置存储
  static #configs = new Map();
  static #defaultConfig = {
    theme: 'light',
    language: 'zh-CN',
    apiTimeout: 5000,
    retryAttempts: 3
  };
  
  // 环境配置
  static #environments = {
    development: {
      apiUrl: 'http://localhost:3000',
      debug: true,
      logLevel: 'debug'
    },
    production: {
      apiUrl: 'https://api.example.com',
      debug: false,
      logLevel: 'error'
    }
  };
  
  /**
   * 初始化配置
   * @param {string} environment - 环境名称
   */
  static initialize(environment = 'development') {
    const envConfig = ConfigManager.#environments[environment] || {};
    const config = { ...ConfigManager.#defaultConfig, ...envConfig };
    
    ConfigManager.#configs.set('global', config);
    console.log(`Configuration initialized for ${environment} environment`);
  }
  
  /**
   * 获取配置值
   * @param {string} key - 配置键
   * @param {string} namespace - 命名空间
   * @returns {any} 配置值
   */
  static get(key, namespace = 'global') {
    const config = ConfigManager.#configs.get(namespace);
    return config ? config[key] : undefined;
  }
  
  /**
   * 设置配置值
   * @param {string} key - 配置键
   * @param {any} value - 配置值
   * @param {string} namespace - 命名空间
   */
  static set(key, value, namespace = 'global') {
    if (!ConfigManager.#configs.has(namespace)) {
      ConfigManager.#configs.set(namespace, {});
    }
    
    const config = ConfigManager.#configs.get(namespace);
    config[key] = value;
  }
  
  /**
   * 获取所有配置
   * @param {string} namespace - 命名空间
   * @returns {Object} 配置对象
   */
  static getAll(namespace = 'global') {
    return { ...ConfigManager.#configs.get(namespace) };
  }
}

6. 静态初始化块(Static Initialization Blocks):高级初始化

🔍 应用场景

使用static {}块在类加载时执行复杂的静态初始化逻辑,适用于需要复杂计算或异步操作的静态属性初始化。

❌ 常见问题

传统的静态初始化方式存在局限:

// ❌ 传统方式:静态初始化的局限性
class DatabaseConnection {
  // 问题1:无法进行复杂的初始化逻辑
  static connectionPool = new Map(); // 简单初始化
  
  // 问题2:需要额外的初始化方法
  static initializePool() {
    // 复杂的初始化逻辑需要手动调用
    for (let i = 0; i < 10; i++) {
      this.connectionPool.set(`conn_${i}`, {
        id: `conn_${i}`,
        isActive: false,
        createdAt: new Date()
      });
    }
  }
  
  // 问题3:无法保证初始化顺序
  static maxConnections = DatabaseConnection.connectionPool.size; // 可能为0
}

// 需要手动调用初始化
DatabaseConnection.initializePool();

✅ 推荐方案

使用静态初始化块实现复杂的类初始化:

/**
 * 数据库连接池类 - 使用静态初始化块
 * @description 展示静态初始化块的高级用法
 */
class DatabaseConnection {
  // ✅ 静态字段声明
  static connectionPool = new Map();
  static availableConnections = [];
  static busyConnections = new Set();
  static maxConnections = 10;
  static minConnections = 2;
  static connectionTimeout = 30000;
  static retryAttempts = 3;
  
  // ✅ 配置对象
  static config = {};
  static statistics = {};
  static eventListeners = new Map();
  
  // ✅ 静态初始化块 - 复杂的初始化逻辑
  static {
    console.log('Initializing DatabaseConnection class...');
    
    // 初始化配置
    this.config = {
      host: process.env.DB_HOST || 'localhost',
      port: parseInt(process.env.DB_PORT) || 5432,
      database: process.env.DB_NAME || 'myapp',
      ssl: process.env.NODE_ENV === 'production',
      poolSize: this.maxConnections,
      idleTimeout: this.connectionTimeout,
      acquireTimeout: 10000
    };
    
    // 初始化统计信息
    this.statistics = {
      totalConnections: 0,
      activeConnections: 0,
      totalQueries: 0,
      failedConnections: 0,
      averageResponseTime: 0,
      startTime: new Date()
    };
    
    // 创建初始连接池
    this.initializeConnectionPool();
    
    // 设置定时清理任务
    this.setupCleanupTasks();
    
    // 注册进程退出处理
    this.registerExitHandlers();
    
    console.log(`DatabaseConnection initialized with ${this.connectionPool.size} connections`);
  }
  
  /**
   * 静态初始化块中调用的初始化方法
   */
  static initializeConnectionPool() {
    // 创建最小数量的连接
    for (let i = 0; i < this.minConnections; i++) {
      const connection = this.createConnection(`conn_${i}`);
      this.connectionPool.set(connection.id, connection);
      this.availableConnections.push(connection.id);
    }
    
    this.statistics.totalConnections = this.connectionPool.size;
  }
  
  /**
   * 创建数据库连接
   * @param {string} id - 连接ID
   * @returns {Object} 连接对象
   */
  static createConnection(id) {
    return {
      id,
      isActive: false,
      createdAt: new Date(),
      lastUsed: null,
      queryCount: 0,
      errorCount: 0,
      status: 'idle'
    };
  }
  
  /**
   * 设置清理任务
   */
  static setupCleanupTasks() {
    // 每30秒检查空闲连接
    setInterval(() => {
      this.cleanupIdleConnections();
    }, 30000);
    
    // 每5分钟更新统计信息
    setInterval(() => {
      this.updateStatistics();
    }, 300000);
  }
  
  /**
   * 注册进程退出处理
   */
  static registerExitHandlers() {
    const cleanup = () => {
      console.log('Closing database connections...');
      this.closeAllConnections();
    };
    
    process.on('SIGINT', cleanup);
    process.on('SIGTERM', cleanup);
    process.on('exit', cleanup);
  }
  
  /**
   * 获取可用连接
   * @returns {Promise<string>} 连接ID
   */
  static async getConnection() {
    // 如果有可用连接,直接返回
    if (this.availableConnections.length > 0) {
      const connectionId = this.availableConnections.pop();
      this.busyConnections.add(connectionId);
      
      const connection = this.connectionPool.get(connectionId);
      connection.isActive = true;
      connection.lastUsed = new Date();
      connection.status = 'active';
      
      this.statistics.activeConnections++;
      return connectionId;
    }
    
    // 如果连接池未满,创建新连接
    if (this.connectionPool.size < this.maxConnections) {
      const newId = `conn_${this.connectionPool.size}`;
      const connection = this.createConnection(newId);
      
      this.connectionPool.set(newId, connection);
      this.busyConnections.add(newId);
      
      connection.isActive = true;
      connection.lastUsed = new Date();
      connection.status = 'active';
      
      this.statistics.totalConnections++;
      this.statistics.activeConnections++;
      
      return newId;
    }
    
    // 等待连接可用
    return this.waitForConnection();
  }
  
  /**
   * 等待连接可用
   * @returns {Promise<string>} 连接ID
   */
  static waitForConnection() {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Connection acquisition timeout'));
      }, this.config.acquireTimeout);
      
      const checkConnection = () => {
      if (this.availableConnections.length > 0) {
        clearTimeout(timeout);
        resolve(this.getConnection());
      } else {
        setTimeout(checkConnection, 100);
      }
    };
    
    checkConnection();
  });
}

/**
 * 释放连接
 * @param {string} connectionId - 连接ID
 */
static releaseConnection(connectionId) {
  if (this.busyConnections.has(connectionId)) {
    this.busyConnections.delete(connectionId);
    this.availableConnections.push(connectionId);
    
    const connection = this.connectionPool.get(connectionId);
    if (connection) {
      connection.isActive = false;
      connection.status = 'idle';
      this.statistics.activeConnections--;
    }
  }
}

/**
 * 清理空闲连接
 */
static cleanupIdleConnections() {
  const now = new Date();
  const idleThreshold = this.connectionTimeout;
  
  this.availableConnections.forEach((connectionId, index) => {
    const connection = this.connectionPool.get(connectionId);
    if (connection && connection.lastUsed) {
      const idleTime = now - connection.lastUsed;
      if (idleTime > idleThreshold && this.connectionPool.size > this.minConnections) {
        this.connectionPool.delete(connectionId);
        this.availableConnections.splice(index, 1);
        this.statistics.totalConnections--;
      }
    }
  });
}

/**
 * 更新统计信息
 */
static updateStatistics() {
  this.statistics.uptime = new Date() - this.statistics.startTime;
  this.statistics.poolUtilization = (this.busyConnections.size / this.maxConnections) * 100;
  
  console.log('Database Pool Statistics:', {
    totalConnections: this.statistics.totalConnections,
    activeConnections: this.statistics.activeConnections,
    poolUtilization: `${this.statistics.poolUtilization.toFixed(2)}%`,
    uptime: `${Math.floor(this.statistics.uptime / 1000)}s`
  });
}

/**
 * 关闭所有连接
 */
static closeAllConnections() {
  this.connectionPool.clear();
  this.availableConnections.length = 0;
  this.busyConnections.clear();
  this.statistics.totalConnections = 0;
  this.statistics.activeConnections = 0;
}

/**
 * 获取连接池状态
 * @returns {Object} 连接池状态
 */
static getPoolStatus() {
  return {
    total: this.connectionPool.size,
    available: this.availableConnections.length,
    busy: this.busyConnections.size,
    maxConnections: this.maxConnections,
    utilization: (this.busyConnections.size / this.maxConnections) * 100
  };
}
}

/**
 * 缓存管理类 - 展示多个静态初始化块
 * @description 使用多个静态初始化块进行分阶段初始化
 */
class CacheManager {
// ✅ 基础静态字段
static cache = new Map();
static config = {};
static metrics = {};

// ✅ 第一个静态初始化块 - 基础配置
static {
  console.log('Phase 1: Initializing basic cache configuration...');
  
  this.config = {
    maxSize: parseInt(process.env.CACHE_MAX_SIZE) || 1000,
    ttl: parseInt(process.env.CACHE_TTL) || 300000, // 5分钟
    cleanupInterval: parseInt(process.env.CACHE_CLEANUP_INTERVAL) || 60000, // 1分钟
    compressionEnabled: process.env.CACHE_COMPRESSION === 'true',
    persistenceEnabled: process.env.CACHE_PERSISTENCE === 'true'
  };
  
  console.log('Basic configuration loaded:', this.config);
}

// ✅ 第二个静态初始化块 - 指标初始化
static {
  console.log('Phase 2: Initializing cache metrics...');
  
  this.metrics = {
    hits: 0,
    misses: 0,
    sets: 0,
    deletes: 0,
    evictions: 0,
    totalSize: 0,
    startTime: new Date()
  };
  
  // 计算命中率的getter
  Object.defineProperty(this.metrics, 'hitRate', {
    get() {
      const total = this.hits + this.misses;
      return total > 0 ? (this.hits / total * 100).toFixed(2) : 0;
    }
  });
  
  console.log('Metrics system initialized');
}

// ✅ 第三个静态初始化块 - 高级功能
static {
  console.log('Phase 3: Setting up advanced cache features...');
  
  // 设置定时清理
  setInterval(() => {
    this.cleanup();
  }, this.config.cleanupInterval);
  
  // 设置指标报告
  setInterval(() => {
    this.reportMetrics();
  }, 300000); // 每5分钟报告一次
  
  // 如果启用持久化,加载缓存数据
  if (this.config.persistenceEnabled) {
    this.loadPersistedCache();
  }
  
  console.log('Advanced features initialized');
  console.log('CacheManager fully initialized and ready to use');
}

/**
 * 设置缓存项
 * @param {string} key - 缓存键
 * @param {any} value - 缓存值
 * @param {number} customTtl - 自定义TTL
 */
static set(key, value, customTtl = null) {
  const ttl = customTtl || this.config.ttl;
  const expiresAt = new Date(Date.now() + ttl);
  
  // 如果缓存已满,执行LRU淘汰
  if (this.cache.size >= this.config.maxSize && !this.cache.has(key)) {
    this.evictLRU();
  }
  
  const cacheItem = {
    value,
    expiresAt,
    accessCount: 0,
    lastAccessed: new Date(),
    size: this.calculateSize(value)
  };
  
  this.cache.set(key, cacheItem);
  this.metrics.sets++;
  this.updateTotalSize();
}

/**
 * 获取缓存项
 * @param {string} key - 缓存键
 * @returns {any} 缓存值或undefined
 */
static get(key) {
  const item = this.cache.get(key);
  
  if (!item) {
    this.metrics.misses++;
    return undefined;
  }
  
  // 检查是否过期
  if (new Date() > item.expiresAt) {
    this.cache.delete(key);
    this.metrics.misses++;
    this.updateTotalSize();
    return undefined;
  }
  
  // 更新访问信息
  item.accessCount++;
  item.lastAccessed = new Date();
  this.metrics.hits++;
  
  return item.value;
}

/**
 * LRU淘汰策略
 */
static evictLRU() {
  let oldestKey = null;
  let oldestTime = new Date();
  
  for (const [key, item] of this.cache) {
    if (item.lastAccessed < oldestTime) {
      oldestTime = item.lastAccessed;
      oldestKey = key;
    }
  }
  
  if (oldestKey) {
    this.cache.delete(oldestKey);
    this.metrics.evictions++;
    this.updateTotalSize();
  }
}

/**
 * 清理过期项
 */
static cleanup() {
  const now = new Date();
  let cleanedCount = 0;
  
  for (const [key, item] of this.cache) {
    if (now > item.expiresAt) {
      this.cache.delete(key);
      cleanedCount++;
    }
  }
  
  if (cleanedCount > 0) {
    this.updateTotalSize();
    console.log(`Cleaned up ${cleanedCount} expired cache items`);
  }
}

/**
 * 计算值的大小
 * @param {any} value - 要计算的值
 * @returns {number} 大小(字节)
 */
static calculateSize(value) {
  return JSON.stringify(value).length * 2; // 简化的大小计算
}

/**
 * 更新总大小
 */
static updateTotalSize() {
  this.metrics.totalSize = Array.from(this.cache.values())
    .reduce((total, item) => total + item.size, 0);
}

/**
 * 报告指标
 */
static reportMetrics() {
  console.log('Cache Metrics Report:', {
    hitRate: `${this.metrics.hitRate}%`,
    totalItems: this.cache.size,
    totalSize: `${(this.metrics.totalSize / 1024).toFixed(2)} KB`,
    hits: this.metrics.hits,
    misses: this.metrics.misses,
    evictions: this.metrics.evictions
  });
}

/**
 * 加载持久化缓存
 */
static loadPersistedCache() {
  // 实际项目中会从文件或数据库加载
  console.log('Loading persisted cache data...');
}
}

💡 核心要点

  • 复杂初始化:静态初始化块支持复杂的初始化逻辑
  • 执行顺序:多个静态初始化块按声明顺序执行
  • 异步支持:可以在初始化块中设置异步任务
  • 私有访问:可以访问私有静态字段和方法

🎯 实际应用

在微服务配置中的静态初始化块应用:

// 实际项目中的微服务配置应用
class ServiceRegistry {
  static services = new Map();
  static healthChecks = new Map();
  static loadBalancer = null;
  
  static {
    // 初始化服务发现
    this.initializeServiceDiscovery();
    
    // 设置健康检查
    this.setupHealthChecks();
    
    // 配置负载均衡
    this.configureLoadBalancer();
    
    console.log('ServiceRegistry initialized with service discovery');
  }
  
  static initializeServiceDiscovery() {
    // 从环境变量或配置文件加载服务
    const serviceConfig = process.env.SERVICES_CONFIG || '[]';
    const services = JSON.parse(serviceConfig);
    
    services.forEach(service => {
      this.registerService(service.name, service.endpoints);
    });
  }
  
  static registerService(name, endpoints) {
    this.services.set(name, {
      endpoints: endpoints.map(ep => ({ ...ep, healthy: true })),
      lastCheck: new Date(),
      requestCount: 0
    });
  }
  
  static setupHealthChecks() {
    setInterval(() => {
      this.performHealthChecks();
    }, 30000); // 每30秒检查一次
  }
  
  static async performHealthChecks() {
    for (const [serviceName, service] of this.services) {
      for (const endpoint of service.endpoints) {
        try {
          const response = await fetch(`${endpoint.url}/health`, {
            timeout: 5000
          });
          endpoint.healthy = response.ok;
        } catch (error) {
          endpoint.healthy = false;
        }
      }
    }
  }
}

📊 特性对比总结

特性 使用场景 优势 注意事项
构造函数 实例初始化 自动调用、参数验证 必须同步、避免复杂逻辑
extends继承 代码复用、多态 层次结构清晰、方法重写 避免过深继承、正确使用super
私有字段 数据封装 真正私有、编译时检查 不能被继承、语法较新
公有字段 属性声明 结构清晰、自动绑定 实例属性、默认值设置
static方法 工具函数、工厂 无需实例化、类级别访问 不能访问实例属性
静态初始化块 复杂初始化 支持复杂逻辑、执行顺序 语法较新、浏览器兼容性

🎯 实际应用建议

1. 选择合适的特性组合

// ✅ 推荐的特性组合使用
class ModernComponent {
  // 公有字段声明
  state = { loading: false };
  
  // 私有字段保护敏感数据
  #apiKey = process.env.API_KEY;
  
  // 静态工具方法
  static createInstance(config) {
    return new ModernComponent(config);
  }
  
  // 构造函数处理必需参数
  constructor(config) {
    this.config = { ...this.defaultConfig, ...config };
  }
  
  // 实例方法使用箭头函数自动绑定
  handleAction = async () => {
    this.state.loading = true;
    try {
      await this.performAction();
    } finally {
      this.state.loading = false;
    }
  };
}

2. 性能优化考虑

  • 静态方法:用于不依赖实例状态的工具函数
  • 私有字段:比WeakMap方式性能更好
  • 公有字段:避免在构造函数中重复声明

3. 代码组织最佳实践

  • 分离关注点:构造函数只做初始化,复杂逻辑分离到其他方法
  • 合理继承:避免过深的继承链,优先组合而非继承
  • 静态初始化:用于一次性的复杂设置和配置

🎉 总结

JavaScript类的现代特性为我们提供了强大的面向对象编程能力:

  1. 构造函数提供了可靠的实例初始化机制
  2. extends继承实现了优雅的代码复用和扩展
  3. 私有字段带来了真正的数据封装和安全性
  4. 公有字段简化了属性声明和类结构
  5. static特性提供了类级别的功能和工具方法
  6. 静态初始化块支持复杂的类初始化逻辑

掌握这些特性,你就能写出更加专业、安全、高效的JavaScript代码。在实际项目中,合理组合使用这些特性,将大大提升代码的可维护性和开发效率!


🔗 相关资源

正确的 .gitignore 配置

提交代码时 执行 pod install 后,.xcodeproj 文件被修改了,产生了待提交的内容。 原因分析 当你运行 pod install 时,CocoaPods 会: ✅ 在 Pods/ 目
❌