Day03_ES6 深度解析与实战应用:运算符、Symbol、Class、集合与迭代协议

ES6 深度解析与实战应用:运算符、Symbol、Class、集合与迭代协议

本文系统梳理 ES2015 及后续规范中的新增运算符SymbolClass 语法Set / Map 家族Iterator / Iterable 协议Generator,结合可运行示例与归纳表,帮助你在工程实践中选对工具、写出更安全、可维护的现代 JavaScript。

目录

  • [0. 知识脉络与学习地图](#0. 知识脉络与学习地图)
  • [1. 新增运算符详解](#1. 新增运算符详解)
    • [1.1 指数运算符](#1.1 指数运算符)
    • [1.2 可选链运算符](#1.2 可选链运算符)
    • [1.3 空值判断运算符](#1.3 空值判断运算符)
    • [1.4 逻辑赋值运算符](#1.4 逻辑赋值运算符)
  • [2. Symbol 类型深度解析](#2. Symbol 类型深度解析)
    • [2.1 Symbol 核心特性](#2.1 Symbol 核心特性)
    • [2.2 Symbol 创建与使用](#2.2 Symbol 创建与使用)
    • [2.3 Symbol 应用场景](#2.3 Symbol 应用场景)
    • [2.4 全局 Symbol 与 Well-known Symbol](#2.4 全局 Symbol 与 Well-known Symbol)
    • [2.5 Well-known Symbol 实战(toPrimitive / iterator)](#2.5 Well-known Symbol 实战(toPrimitive / iterator))
  • [3. Class 语法全面指南](#3. Class 语法全面指南)
    • [3.1 类的定义与实例化](#3.1 类的定义与实例化)
    • [3.2 构造器方法](#3.2 构造器方法)
    • [3.3 私有属性](#3.3 私有属性)
    • [3.4 静态方法](#3.4 静态方法)
    • [3.5 Getter 与 Setter 访问器属性](#3.5 Getter 与 Setter 访问器属性)
    • [3.6 类的继承](#3.6 类的继承)
    • [3.7 继承内置类](#3.7 继承内置类)
    • [3.8 super 关键字](#3.8 super 关键字)
    • [3.9 Mixin 模式:弥补单继承的局限](#3.9 Mixin 模式:弥补单继承的局限)
  • [4. Set 与 Map 数据结构](#4. Set 与 Map 数据结构)
    • [4.1 Set(含集合运算)](#4.1 Set(含集合运算))
    • [4.2 WeakSet](#4.2 WeakSet)
    • [4.3 Map(含 LRU Cache)](#4.3 Map(含 LRU Cache))
    • [4.4 WeakMap](#4.4 WeakMap)
  • [5. 遍历器与可遍历对象](#5. 遍历器与可遍历对象)
    • [5.1 Iterator 遍历器](#5.1 Iterator 遍历器)
    • [5.2 Iterable 可遍历对象](#5.2 Iterable 可遍历对象)
    • [5.3 Iterator 与 Iterable 的关系](#5.3 Iterator 与 Iterable 的关系)
    • [5.5 for...of 与 Iterable 触发场景](#5.5 for…of 与 Iterable 触发场景)
    • [5.6 伪数组与可遍历对象的区别](#5.6 伪数组与可遍历对象的区别)
  • [6. 生成器 Generator](#6. 生成器 Generator)
    • [6.1 基本语法](#6.1 基本语法)
    • [6.2 工作原理](#6.2 工作原理)
    • [6.3 为对象部署 Iterator 接口](#6.3 为对象部署 Iterator 接口)
    • [6.4 高级应用(无限序列 / yield* / 状态机)](#6.4 高级应用(无限序列 / yield* / 状态机))
  • [7. 附录:扩展运算符与浅拷贝](#7. 附录:扩展运算符与浅拷贝)
  • [8. 总结与最佳实践](#8. 总结与最佳实践)

0. 知识脉络与学习地图

Day03 的核心可以概括为:用更安全的语法操作数据 → 用 Symbol / Class 组织对象 → 用 Set / Map 存储集合 → 用 Iterator / Generator 统一遍历
#mermaid-svg-wsXh7aPFUsj3X8sN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wsXh7aPFUsj3X8sN .error-icon{fill:#552222;}#mermaid-svg-wsXh7aPFUsj3X8sN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wsXh7aPFUsj3X8sN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wsXh7aPFUsj3X8sN .marker.cross{stroke:#333333;}#mermaid-svg-wsXh7aPFUsj3X8sN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wsXh7aPFUsj3X8sN p{margin:0;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge{stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .section--1 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section--1 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section--1 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section--1 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section--1 text{fill:#ffffff;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth--1{stroke-width:17;}#mermaid-svg-wsXh7aPFUsj3X8sN .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-0 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-0 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-0 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-0 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-0 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-0{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-0{stroke-width:14;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-1 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-1 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-1 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-1 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-1 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-1{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-1{stroke-width:11;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 text{fill:#ffffff;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-2{stroke-width:8;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-3 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-3 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-3 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-3 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-3 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-3{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-3{stroke-width:5;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-4 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-4 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-4 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-4 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-4 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-4{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-4{stroke-width:2;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-5 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-5 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-5 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-5 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-5 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-5{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-5{stroke-width:-1;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-6 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-6 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-6 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-6 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-6 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-6{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-6{stroke-width:-4;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-7 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-7 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-7 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-7 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-7 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-7{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-7{stroke-width:-7;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-8 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-8 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-8 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-8 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-8 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-8{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-8{stroke-width:-10;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-9 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-9 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-9 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-9 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-9 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-9{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-9{stroke-width:-13;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-10 rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-10 path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-10 circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-10 polygon,#mermaid-svg-wsXh7aPFUsj3X8sN .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-10 text{fill:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .node-icon-10{font-size:40px;color:black;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .edge-depth-10{stroke-width:-16;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled circle,#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:lightgray;}#mermaid-svg-wsXh7aPFUsj3X8sN .disabled text{fill:#efefef;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-root rect,#mermaid-svg-wsXh7aPFUsj3X8sN .section-root path,#mermaid-svg-wsXh7aPFUsj3X8sN .section-root circle,#mermaid-svg-wsXh7aPFUsj3X8sN .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-wsXh7aPFUsj3X8sN .section-root text{fill:#ffffff;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-root span{color:#ffffff;}#mermaid-svg-wsXh7aPFUsj3X8sN .section-2 span{color:#ffffff;}#mermaid-svg-wsXh7aPFUsj3X8sN .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-wsXh7aPFUsj3X8sN .edge{fill:none;}#mermaid-svg-wsXh7aPFUsj3X8sN .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-wsXh7aPFUsj3X8sN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ES6 Day03
运算符
指数 **
可选链 ?.
空值合并 ??
逻辑赋值 &&= ||= ??=
类型与 OOP
Symbol 唯一键
Class 语法糖
私有字段 #
extends / super
集合
Set / WeakSet
Map / WeakMap
迭代
Iterator.next
Iterable Symbol.iterator
Generator yield

【代码注释】Day03 四条主线构成完整学习路径:运算符?./?? 减少判空与运行时异常)→ Symbol / Class (唯一键与结构化 OOP)→ Set / Map (去重、键值映射、弱引用防泄漏)→ Iterator / Generator(统一遍历协议与惰性序列)。建议按图中顺序学习,并在项目中对照选型表实践。

模块 规范要点(MDN / ECMAScript) 工程价值
运算符 ES2016 **、ES2020 ?. ??、ES2021 逻辑赋值 减少判空样板代码、避免运行时异常
Symbol 原始类型,不可 newSymbol.for 全局注册 防属性名冲突、元编程钩子
Class 本质是构造函数语法糖,typeoffunction 结构化 OOP、与 TS 类型系统衔接
Set / Map 可迭代、键值语义与 Object 不同 去重、缓存、元数据挂载
Iterator / Generator 迭代协议:next(){ value, done } 自定义遍历、惰性序列、异步流程基础

1. 新增运算符详解

ES6 及后续版本引入了一批语义明确、可组合 的运算符。它们与 &&|| 等经典逻辑运算符配合,能显著减少「层层判空」的样板代码,并在 TypeScript / Babel 生态中得到广泛支持。

运算符总览表

运算符 含义 操作数个数 操作数要求 表达式值类型 副作用
** 指数 2 一般为 number number
?. 可选链 1(链式多次) 无特殊限制 任意或 undefined
?? 空值合并 2 无特殊限制 左或右操作数类型
&&= 逻辑与赋值 2 左侧必须是变量 赋值后的左值
` =` 逻辑或赋值 2 左侧必须是变量
??= 空值合并赋值 2 左侧必须是变量 赋值后的左值

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

仅 null/undefined
其他 falsy 如 0
访问深层属性
中间层为 null/undefined?
?. 短路 → undefined
继续访问
需要默认值?
?? 取右侧
保留原值

【代码注释】流程图要点:?.任意中间层null/undefined短路 ,整条链结果为 undefined,不抛 TypeError?? 仅识别 Nullishnull/undefined),0''false 会保留,适合「配置缺省」而非「falsy 兜底」。二者常组合:user?.profile?.name ?? '匿名'

1.1 指数运算符

语法基础

指数运算符 ** 是ES7中引入的新特性,用于计算幂运算,它等同于 Math.pow() 方法。

javascript 复制代码
// 【代码注释】ES2016 指数运算符:左侧为底数,右侧为指数,语义等价于 Math.pow(base, exponent)
12 ** 2;   // 144 --- 12 的平方
2 ** 10;   // 1024 --- 2 自乘 10 次
89 ** 3;   // 704969 --- 大数幂运算,注意结果可能超出 Number 安全整数时需 BigInt

【代码注释】**右结合2 ** 3 ** 2 先算 3 ** 2 = 9,再算 2 ** 9 = 512(不是 8 ** 2)。底数、指数须为 Number(或可被 ToNumber 的值);负指数表示倒数(2 ** -2 === 0.25)。优先级高于 *//,低于一元运算符。

名词解释
  • 底数 :幂运算中的基础数值,如 2 ** 3 中的 2
  • 指数 :表示底数需要自乘的次数,如 2 ** 3 中的 3
  • :运算的结果值
完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>指数运算符示例</title>
</head>
<body>
    <script>
        // 【代码注释】底数 12、指数 2:计算平方,结果类型为 number
        console.log(12 ** 2);  // 输出:144

        // 【代码注释】2 的 10 次方 = 1024,常用于容量、分页总数等 2 的幂场景
        console.log(2 ** 10);  // 输出:1024
        
        // 【代码注释】任意正整数底数均可;结果仍在 IEEE 754 可表示范围内
        console.log(89 ** 3);  // 输出:704969
        
        // 【代码注释】规范层面 ** 与 Math.pow 等价;迁移旧代码时可全局替换,注意 Math.pow 对非整数指数的行为一致
        console.log(2 ** 10 === Math.pow(2, 10));  // 输出:true
        
        // 【代码注释】负指数等价于 1 / (base ** |exp|),即 2^(-2) = 1/4 = 0.25
        console.log(2 ** -2);  // 输出:0.25,即 1/4
        
        // 【代码注释】** 优先级高于 *:先 2**3=8,再 8*4=32;若需 (2**3)*4 与 2**(3*4) 不同结果,须加括号
        console.log(2 ** 3 * 4);  // 输出:32 --- 等价于 (2 ** 3) * 4
    </script>
</body>
</html>

【代码注释】可运行 HTML 示例小结:**Math.pow 等价且右结合;支持负指数、小数指数(如 9 ** 0.5)。工程注意:超大整数幂用 BigInt2n ** 10n);混合类型会触发 ToNumber 转换。

经典应用场景
  1. 科学计算:在涉及物理、数学计算时,指数运算符提供了简洁的表示方式
  2. 算法实现:在动态规划、分治算法中经常涉及幂运算
  3. 数据处理:在处理指数增长或衰减的数据模型时
市面应用实例
  • 金融科技:计算复利时,如计算投资回报率
  • 游戏开发:经验值计算、伤害公式设计
  • 数据可视化:在D3.js等库中进行数据缩放转换

1.2 可选链运算符

语法核心

可选链运算符 ?. 用于安全地访问嵌套对象属性,当属性不存在时返回 undefined 而不是抛出错误。

基本用法
javascript 复制代码
// 【代码注释】属性链:任一层为 null/undefined 则短路,不访问后续属性
对象?.属性名
对象?.属性名?.属性名

// 【代码注释】方法可选调用:仅当左侧求值得到函数时才执行,否则整个表达式为 undefined(非抛错)
对象.方法名?.()

// 【代码注释】计算属性与下标同样支持可选链,常用于 API 字段名不确定或数组越界防护
对象?.[表达式]
arr?.[index]

【代码注释】可选链 在左侧为 null/undefined 时短路;0''false 会继续访问(可能仍抛错)。?. 不能 用于赋值左侧(obj?.x = 1 非法)。方法调用写 obj.m?.() 而非 obj?.m(),后者在 obj 为空时仍会尝试读取 m

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>可选链运算符示例</title>
</head>
<body>
    <script>
        // 【代码注释】模拟接口返回的用户结构:含嵌套 child、方法 say;parent 字段故意缺失,用于对比安全访问
        const user = {
            address: '浦东新区',
            age: 12,
            child: {
                age: 2,
                address: '朝阳区'
            },
            say() {
                return 'say';
            }
        };
        
        // 【代码注释】点语法在路径完整时行为不变;child 存在时深层访问正常
        console.log(user.address);       // 输出:'浦东新区'
        console.log(user.child.address); // 输出:'朝阳区'
        
        // 【代码注释】user.parent 不存在 → undefined(不报错);但若写 user.parent.address 会抛 TypeError
        console.log(user.parent);       // 输出:undefined
        
        // 【代码注释】?. 在 parent 为 undefined 时短路,user?.parent?.address 整体为 undefined 而非抛错
        console.log(user?.address);         // 输出:'浦东新区'
        console.log(user?.child?.address);  // 输出:'朝阳区'
        console.log(user?.parent);          // 输出:undefined
        console.log(user?.parent?.address); // 输出:undefined,不会报错
        
        // 【代码注释】say?.():say 存在则调用;eat?.():eat 不存在返回 undefined,不会像 eat() 那样抛 TypeError
        console.log(user.say?.());    // 输出:'say',方法存在时正常调用
        console.log(user.eat?.());    // 输出:undefined,方法不存在时返回undefined
        
        // 【代码注释】数组越界时 users[5] 为 undefined,配合 ?. 避免对 undefined 取属性;等价于 (users[5] ?? {}).name 的简化写法
        const users = [{name: 'Alice'}, {name: 'Bob'}];
        console.log(users[0]?.name);  // 输出:'Alice'
        console.log(users[5]?.name);  // 输出:undefined
    </script>
</body>
</html>

【代码注释】可运行示例小结:?. 解决「中间层缺失」类错误,典型用于 res?.data?.list?.[0]?.id。注意:?.?? 组合时,undefined ?? 默认值 才生效;?. 单独不能区分「字段缺失」与「字段值为 null」。

技术优势
  1. 代码简洁性 :减少了大量的 if 判断和 && 短路逻辑
  2. 错误防范:避免因属性不存在而导致的运行时错误
  3. 可读性提升:代码意图更加明确,表达"安全访问"的语义
经典应用场景
  1. API响应处理:处理可能缺失字段的API数据
  2. 表单验证:安全地访问嵌套的表单数据
  3. 配置管理:访问可能不存在的配置项
市面应用实例
  • React应用:访问props中的嵌套数据
  • Vue应用:处理可能为空的响应式数据
  • Angular应用:安全访问服务返回的数据结构

1.3 空值判断运算符

语法原理

空值判断运算符 ?? 是一个逻辑运算符,当左侧的操作数为 nullundefined 时,返回右侧的操作数,否则返回左侧的操作数。

与逻辑或运算符的区别
javascript 复制代码
// 【代码注释】|| 左侧为 falsy(含 0、''、false、null、undefined、NaN)时取右侧
0 || 100;        // 100 --- 0 被当作「无有效值」

// 【代码注释】?? 仅 null/undefined 取右侧;0、''、false 视为业务有效值而保留
0 ?? 100;        // 0 --- 分页 page=0、价格 0 元等场景必须用 ??

【代码注释】??Nullish Coalescing :仅当左侧为 nullundefined 取右侧。与 || 对比:0 || 100→100,但 0 ?? 100→0。表单数值、开关状态、分页 page=0 等合法 falsy 场景必须用 ?? 设默认值。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>空值判断运算符示例</title>
</head>
<body>
    <script>
        // 【代码注释】`||` 左侧为任意 falsy(0、''、false、null、undefined、NaN)都会取右侧 --- 易误伤合法业务值
        console.log(100 || 'hello');     // 100
        console.log(false || 'hello');   // 'hello'
        console.log(0 || 'hello');       // 'hello'
        console.log(undefined || 'hello'); // 'hello'
        console.log(null || 'hello');    // 'hello'
        console.log('' || 'hello');      // 'hello'
        
        console.log('---分隔线---');
        
        // 【代码注释】`??` 仅 null/undefined 取右侧;0、false、'' 被视为「用户明确传入的有效值」而保留
        console.log(100 ?? 'hello');     // 100,100不是空值
        console.log(0 ?? 'hello');       // 0,0不是空值
        console.log(false ?? 'hello');   // false,false不是空值
        console.log(undefined ?? 'hello'); // 'hello',undefined 是空值
        console.log(null ?? 'hello');      // 'hello',null 是空值
        console.log('' ?? 'hello');      // '',空字符串不是空值
        
        // 【代码注释】工厂函数:演示 age=0 与 age 未传时的差异 --- 用 ?? 才能保留 0 岁
        function createUser(name, age) {
            // 【代码注释】name ?? 'Anonymous':仅 name 为 nullish 时匿名;age ?? 18:传入 0 时 age 仍为 0,不会被改成 18
            return {
                name: name ?? 'Anonymous',
                age: age ?? 18
            };
        }
        
        console.log(createUser('Alice', 0));     // {name: 'Alice', age: 0}
        console.log(createUser('Bob', null));    // {name: 'Bob', age: 18}
        console.log(createUser('Charlie'));      // {name: 'Charlie', age: 18}
    </script>
</body>
</html>

【代码注释】可运行示例小结:配置/参数默认值优先 ??;需要「任意 falsy 都替换」时才用 ||。ESLint prefer-nullish-coalescing 可辅助团队统一。注意:??&&/|| 混用时须加括号(?? 优先级低于 ||)。

名词解释
  • 空值 :特指 nullundefined 两个值
  • Falsy值 :在布尔上下文中被视为false的值,包括 0false''nullundefinedNaN
  • Nullish Coalescing:空值合并的英文名称
经典应用场景
  1. 配置管理:为配置项提供默认值
  2. 数据处理:处理可能缺失的数据字段
  3. UI渲染:为显示内容提供备选方案
市面应用实例
  • 数据库查询:处理可能为null的查询结果
  • API集成:为API返回的缺失字段设置默认值
  • 表单处理:处理用户未填写的可选字段

1.4 逻辑赋值运算符

语法概览

ES2021引入了三个逻辑赋值运算符:&&=||=??=,它们将逻辑运算与赋值操作结合在一起。

javascript 复制代码
x &&= y;  // 等同于 x = x && y;
x ||= y;  // 等同于 x = x || y;
x ??= y;  // 等同于 x = x ?? y;

【代码注释】ES2021 逻辑赋值:x &&= y 等价 x && (x = y)(左侧 falsy 则不赋值也不求值右侧);x ||= yx ??= y 同理。左侧必须是可赋值的引用 (变量、属性),不能是字面量。右侧表达式在条件不满足时不会执行(惰性)。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>逻辑赋值运算符示例</title>
</head>
<body>
    <script>
        // 【代码注释】演示 &&=、||=、??= 三种形式;注意与逻辑运算符短路规则一致
        let a1 = 10;
        let a2 = 20;
        let a3 = 30;
        
        // 【代码注释】a1 &&= 250:a1 为 truthy 时执行赋值,结果 a1 = (10 && 250) = 250;若 a1 为 0 则保持 0 且不赋 250
        a1 &&= 250;
        console.log('a1:', a1);  // 输出:250
        
        // 【代码注释】a2 ||= 250:仅当 a2 为 falsy 才赋 250;20 为 truthy,故 a2 仍为 20(与 config.debug ||= true 同理)
        a2 ||= 250;
        console.log('a2:', a2);  // 输出:20
        
        // 【代码注释】a3 ??= 250:仅 nullish 时赋值;30 非空,a3 保持 30 --- 配置项「已有值则不覆盖」的首选写法
        a3 ??= 250;
        console.log('a3:', a3);  // 输出:30
        
        // 【代码注释】模拟应用 config 对象:演示 ||= 与 ??= 在真实配置合并中的语义差异
        let config = {
            debug: false,
            logLevel: null
        };
        
        // 【代码注释】debug ||= true:false 为 falsy,故被改为 true;若 debug 已是 true 则不变 --- 适合「未显式开启则默认开启」
        config.debug ||= true;
        console.log('config.debug:', config.debug);  // 输出:true
        
        // 【代码注释】logLevel ??= 'info':null 会被设为 'info';已有 'info' 时再次 ??= 'error' 不会覆盖
        config.logLevel ??= 'info';
        console.log('config.logLevel:', config.logLevel);  // 输出:'info'
        
        // 【代码注释】验证 ??= 的「只填空不覆盖」:已有非 nullish 值时右侧不会生效
        config.logLevel ??= 'error';
        console.log('config.logLevel:', config.logLevel);  // 输出:'info'
    </script>
</body>
</html>

【代码注释】可运行示例小结:初始化 state 常用 options ??= {};缓存赋值用 cache[key] ||= compute()。选型:??= 填 nullish 缺省;||= 填任意 falsy 缺省;&&= 用于「有值才覆盖」的守卫赋值。

技术特点
  1. 惰性求值:只有在需要时才会执行右侧表达式
  2. 链式操作:可以链式使用多个逻辑赋值运算符
  3. 内存优化:避免了不必要的中间变量
经典应用场景
  1. 状态管理:初始化应用状态
  2. 配置合并:为缺失的配置项提供默认值
  3. 条件更新:根据当前值决定是否更新
市面应用实例
  • React Hooks:在自定义Hook中设置初始状态
  • Vue Composition API:管理响应式状态的默认值
  • Node.js配置:加载和管理应用配置

2. Symbol 类型深度解析

Symbol 是 ES2015 新增的第七种原始类型 (与 stringnumber 等并列)。根据 MDN Symbol:每次 Symbol() 调用都返回唯一值;Symbol 不能作为构造函数使用(new Symbol() 会报错);常用作不会冲突的对象属性键

名词解析

术语 说明
原始类型(Primitive) 存储在栈上、按值传递的类型;typeof Symbol()'symbol'
描述字符串(description) Symbol('id') 中括号内字符串,仅用于调试,不影响唯一性
Well-known Symbol 规范预置的 Symbol.iterator 等,用于定制语言内部行为
全局 Symbol 注册表 Symbol.for(key) 在全局复用同一 Symbol;Symbol.keyFor(sym) 反查 key

#mermaid-svg-CvXYriKZWJuVN1xp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CvXYriKZWJuVN1xp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CvXYriKZWJuVN1xp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CvXYriKZWJuVN1xp .error-icon{fill:#552222;}#mermaid-svg-CvXYriKZWJuVN1xp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CvXYriKZWJuVN1xp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CvXYriKZWJuVN1xp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CvXYriKZWJuVN1xp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CvXYriKZWJuVN1xp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CvXYriKZWJuVN1xp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CvXYriKZWJuVN1xp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CvXYriKZWJuVN1xp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CvXYriKZWJuVN1xp .marker.cross{stroke:#333333;}#mermaid-svg-CvXYriKZWJuVN1xp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CvXYriKZWJuVN1xp p{margin:0;}#mermaid-svg-CvXYriKZWJuVN1xp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CvXYriKZWJuVN1xp .cluster-label text{fill:#333;}#mermaid-svg-CvXYriKZWJuVN1xp .cluster-label span{color:#333;}#mermaid-svg-CvXYriKZWJuVN1xp .cluster-label span p{background-color:transparent;}#mermaid-svg-CvXYriKZWJuVN1xp .label text,#mermaid-svg-CvXYriKZWJuVN1xp span{fill:#333;color:#333;}#mermaid-svg-CvXYriKZWJuVN1xp .node rect,#mermaid-svg-CvXYriKZWJuVN1xp .node circle,#mermaid-svg-CvXYriKZWJuVN1xp .node ellipse,#mermaid-svg-CvXYriKZWJuVN1xp .node polygon,#mermaid-svg-CvXYriKZWJuVN1xp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CvXYriKZWJuVN1xp .rough-node .label text,#mermaid-svg-CvXYriKZWJuVN1xp .node .label text,#mermaid-svg-CvXYriKZWJuVN1xp .image-shape .label,#mermaid-svg-CvXYriKZWJuVN1xp .icon-shape .label{text-anchor:middle;}#mermaid-svg-CvXYriKZWJuVN1xp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CvXYriKZWJuVN1xp .rough-node .label,#mermaid-svg-CvXYriKZWJuVN1xp .node .label,#mermaid-svg-CvXYriKZWJuVN1xp .image-shape .label,#mermaid-svg-CvXYriKZWJuVN1xp .icon-shape .label{text-align:center;}#mermaid-svg-CvXYriKZWJuVN1xp .node.clickable{cursor:pointer;}#mermaid-svg-CvXYriKZWJuVN1xp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CvXYriKZWJuVN1xp .arrowheadPath{fill:#333333;}#mermaid-svg-CvXYriKZWJuVN1xp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CvXYriKZWJuVN1xp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CvXYriKZWJuVN1xp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CvXYriKZWJuVN1xp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CvXYriKZWJuVN1xp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CvXYriKZWJuVN1xp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CvXYriKZWJuVN1xp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CvXYriKZWJuVN1xp .cluster text{fill:#333;}#mermaid-svg-CvXYriKZWJuVN1xp .cluster span{color:#333;}#mermaid-svg-CvXYriKZWJuVN1xp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CvXYriKZWJuVN1xp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CvXYriKZWJuVN1xp rect.text{fill:none;stroke-width:0;}#mermaid-svg-CvXYriKZWJuVN1xp .icon-shape,#mermaid-svg-CvXYriKZWJuVN1xp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CvXYriKZWJuVN1xp .icon-shape p,#mermaid-svg-CvXYriKZWJuVN1xp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CvXYriKZWJuVN1xp .icon-shape .label rect,#mermaid-svg-CvXYriKZWJuVN1xp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CvXYriKZWJuVN1xp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CvXYriKZWJuVN1xp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CvXYriKZWJuVN1xp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用途
创建方式
Symbol
Symbol 匿名
Symbol('描述')
Symbol.for 全局 key
对象唯一属性名
调试可读的键
跨文件共享常量

【代码注释】Symbol 三种创建路径:匿名 Symbol() 每次唯一,适合实例级私有键;带描述 Symbol('id') 描述仅影响 String(sym) 调试输出,不保证跨调用相等;全局 Symbol.for(key) 跨文件/模块复用同一引用,适合框架级常量(如 Redux action type)。

2.1 Symbol 的核心特性

javascript 复制代码
// 【代码注释】Symbol 是第七种原始类型,typeof 结果为 'symbol'(非 'object')
typeof Symbol() === 'symbol';  // true

// 【代码注释】即使无描述,两次 Symbol() 也永不相等 --- 这是防属性名冲突的核心保证
Symbol() === Symbol();  // false

// 【代码注释】须用计算属性名 [sym] 写入;点语法 obj.sym 无效(会把 'sym' 当字符串键)
const mySymbol = Symbol();
const obj = {
    [mySymbol]: 'value'  // 键为 Symbol 引用,非字符串 'mySymbol'
};

【代码注释】Symbol 键不会 出现在 for...inObject.keys()JSON.stringify() 中,适合「半私有」元数据。不能 new Symbol()(非构造函数)。与字符串键共存于同一对象,互不影响。

2.2 Symbol的创建与使用

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Symbol类型示例</title>
</head>
<body>
    <script>
        // 【代码注释】无参 Symbol():控制台显示 Symbol(),每次调用生成新唯一值
        let s1 = Symbol();
        let s2 = Symbol();
        
        console.log(s1);              // Symbol()
        console.log(typeof s1);       // 'symbol'
        console.log(s2);              // Symbol(),与s1不同
        console.log(s1 === s2);       // false,每个Symbol都是唯一的
        
        // 【代码注释】描述字符串相同('xiaole')的两个 Symbol 仍不相等 --- 描述不参与相等性判断
        let s3 = Symbol('xiaole');
        let s4 = Symbol('xiaole');
        
        console.log(s3);              // Symbol(xiaole)
        console.log(s4);              // Symbol(xiaole),描述相同但值不同
        console.log(s3 === s4);       // false
        
        // 【代码注释】计算属性 [m]/[s1]/[s3] 写入;注意 [Symbol()] 每次是新键,故后续 user[Symbol()] 取不到值
        const m = 'address';
        const user = {
            [m]: '北京',              // 字符串属性
            [s1]: '上海',             // Symbol属性
            [s3]: '广州',             // 带描述的Symbol属性
            [Symbol()]: '深圳'        // 匿名Symbol属性
        };
        
        console.log(user);
        console.log(user[s1]);        // '上海'
        console.log(user[s3]);        // '广州'
        console.log(user[Symbol()]);  // undefined,每次Symbol()都是新值
        
        // 【代码注释】Symbol 是原始类型,无 [[Construct]];new Symbol() 抛 TypeError。对比:new String() 得到包装对象
        // 【代码注释】取消注释可验证:Symbol 非构造函数,new Symbol() → TypeError
        // new Symbol();
        
        // 【代码注释】包装对象 typeof 为 'object',与 Symbol 原始类型形成对照,理解「原始 vs 包装」
        const msg = new String('Hello World');
        console.log(msg);             // String {'Hello World'}
        console.log(typeof msg);      // 'object'
    </script>
</body>
</html>

【代码注释】可运行示例小结:用变量持有 Symbol 引用再读写属性;Object.getOwnPropertySymbols(obj) 可枚举 Symbol 键。工程上:库扩展第三方对象时用 Symbol 键避免与 toString 等内置名冲突。

2.3 Symbol的应用场景

1. 对象属性私有化

Symbol属性不会出现在 for...inObject.keys() 中,提供了一定程度的私有性。

2. 防止属性名冲突

当多个库或模块需要扩展同一个对象时,使用Symbol作为属性名可以避免冲突。

3. 定义常量

使用Symbol定义一组相关的常量,保证值的唯一性。

javascript 复制代码
// 【代码注释】用 Symbol 作状态「枚举」:每个常量唯一,且与字符串 'pending' 不全等
const STATUS_PENDING = Symbol('pending');
const STATUS_FULFILLED = Symbol('fulfilled');
const STATUS_REJECTED = Symbol('rejected');
// 【代码注释】dispatch({ type: STATUS_PENDING }) 时,外部无法伪造同名 Symbol(除非拿到引用)

【代码注释】Redux/Promise 等状态机常用 Symbol 常量:STATUS_PENDING === 'pending' 为 false,避免与外部字符串或枚举值意外全等;配合 switch 时须用同一 Symbol 引用比较。

2.4 全局 Symbol 与内置 Well-known Symbol

Symbol.for 与 Symbol.keyFor
javascript 复制代码
const s1 = Symbol.for('app.id');
const s2 = Symbol.for('app.id');
console.log(s1 === s2);           // true,同一全局注册项

const local = Symbol('app.id');
console.log(local === s1);      // false,匿名 Symbol 与全局无关

console.log(Symbol.keyFor(s1)); // 'app.id'

【代码注释】Symbol.for('app.id')全局注册表 (跨 realm 共享)查找 key,有则返回已有 Symbol,无则创建并登记。Symbol('app.id')Symbol.for('app.id') 不相等Symbol.keyFor(sym) 反查 key;对 Symbol() 匿名创建返回 undefined

内置 Well-known Symbol(节选)
Symbol 作用
Symbol.iterator 定义默认迭代器,部署后对象变为 Iterable
Symbol.asyncIterator 异步可遍历协议(for await...of
Symbol.toStringTag 定制 Object.prototype.toString 标签
Symbol.hasInstance 定制 instanceof 行为
Symbol.species 派生集合类型时指定构造函数

2.5 Well-known Symbol 实战示例

Symbol.toPrimitive:自定义类型转换
javascript 复制代码
// 【代码注释】Symbol.toPrimitive 拦截所有强制类型转换;hint 为 'number'/'string'/'default' 之一
class Money {
    constructor(amount, currency) {
        this.amount = amount;
        this.currency = currency;
    }

    // 【代码注释】运算符触发 hint='number'(+-*/);字符串模板触发 hint='string';== 触发 hint='default'
    [Symbol.toPrimitive](hint) {
        if (hint === 'number') return this.amount;
        if (hint === 'string') return `${this.amount} ${this.currency}`;
        return this.amount;  // default:用于 == 比较或 + 字符串拼接
    }
}

const price = new Money(99.9, 'CNY');

console.log(+price);           // 99.9 --- hint: 'number',一元 + 触发
console.log(`价格:${price}`);  // '价格:99.9 CNY' --- hint: 'string'
console.log(price + 0.1);      // 100 --- hint: 'default',+ 运算符
console.log(price > 50);       // true --- hint: 'number',比较运算符

【代码注释】Symbol.toPrimitive 优先于 valueOf/toString。三个 hint 覆盖所有隐式转换场景:日期库(Moment/Day.js)用它支持日期对象直接参与数值比较;货币库用它避免 new Money(100).valueOf() 的冗余调用。

Symbol.iterator:为自定义对象部署迭代
javascript 复制代码
// 【代码注释】Range 类部署 Symbol.iterator 后,for...of / 展开 / 解构均可直接使用
class Range {
    constructor(start, end, step = 1) {
        this.start = start;
        this.end = end;
        this.step = step;
    }

    // 【代码注释】generator 函数作为 [Symbol.iterator]:简洁且自动满足 Iterator + Iterable 双协议
    *[Symbol.iterator]() {
        for (let i = this.start; i <= this.end; i += this.step) {
            yield i;
        }
    }
}

const range = new Range(1, 10, 2);
console.log([...range]);         // [1, 3, 5, 7, 9]
console.log(Array.from(range));  // [1, 3, 5, 7, 9]

// 【代码注释】for...of 直接消费,无需手动 next;取前 3 个可配合 take 工具函数
for (const n of range) {
    console.log(n);  // 1, 3, 5, 7, 9
}

// 【代码注释】解构也依赖 Iterable:取前两个区间端点
const [first, second] = range;
console.log(first, second);  // 1, 3

【代码注释】*[Symbol.iterator]() 是定义可遍历类最简洁的写法(Generator + Well-known Symbol 联用)。Range 类在 Python 中内置,JS 中需自定义。生产中可封装 take(n)mapfilter 等惰性管道,在大数据场景避免中间数组。

2.6 经典应用场景

  1. 自定义迭代器:为对象定义默认的迭代行为
  2. 元编程:改变JavaScript引擎的默认行为
  3. 类库开发:为第三方对象添加自定义方法而不冲突

2.7 市面应用实例

  • Redux:使用Symbol定义action类型常量
  • React:内部使用Symbol处理组件的生命周期
  • Lodash:使用Symbol避免方法名冲突

3. Class语法全面指南

ES6的class语法为JavaScript提供了更清晰的面向对象编程方式,但它本质上仍然是基于原型的继承。

3.1 类的定义与实例化

语法结构
javascript 复制代码
// 【代码注释】class 为严格模式;类体顶层仅允许方法、字段、静态块等声明,不能写普通语句
class ClassName {
    // 【代码注释】公有实例字段(ES2022):每个实例一份,在 constructor 前初始化
    property = value;
    
    // 【代码注释】constructor 可选;省略时引擎提供默认 constructor 并继承父类逻辑
    constructor(parameters) {
        // 【代码注释】this 指向新实例;子类中须先 super() 再使用 this
    }
    
    // 【代码注释】简写方法挂载到 ClassName.prototype,所有实例共享
    method() {
        // 【代码注释】可通过 this 访问实例字段与其它实例方法
    }
}

【代码注释】class C {} 在运行时仍创建构造函数 Ctypeof C === 'function'。类体中的简写方法 挂到 C.prototype类字段name = 1)挂到实例自身(ES2022 公有字段)。类声明存在 TDZ,不能在声明前引用。

Class 语法特点归纳
  1. class 定义的「类」本质是构造函数,typeof'function'
  2. 只能 new 实例化,不能像普通函数一样直接调用。
  3. 类体内简写方法 挂在 prototype 上;类字段name = 'xx')挂在实例自身。
  4. 类体内只能写属性、方法声明;其他逻辑应写在方法或构造器内。
完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用class定义类</title>
</head>
<body>
    <script>
        // 【代码注释】class 声明提升行为与 let 类似(TDZ);类名在类体内可用(递归构造)
        class Product {
            // 【代码注释】公有类字段:在 constructor 之前按定义顺序初始化到 this,每个实例一份(非 prototype)
            name = '汽车';
            price = 121.23;
            
            // 【代码注释】buy/addShopcar 等价于 Product.prototype.buy = function...;所有实例共享方法引用
            buy(n) {
                console.log(`购买了${n}件${this.name}`);
            };
            
            addShopcar() {
                console.log('将' + this.name + '加入购物车!');
            };
        }
        
        // 【代码注释】打印 Product 可见 [[FunctionKind]] 为 class;底层仍是 [[Construct]] 可 new 的函数对象
        console.log(Product);           // class Product {...}
        console.log(typeof Product);    // 'function'
        
        // 【代码注释】Product() 抛 TypeError:Class constructor Product cannot be invoked without 'new' --- 与普通函数区分
        // 【代码注释】类不可当普通函数调用;取消注释 Product() 会抛 TypeError
        // Product();
        
        // 【代码注释】new Product() 执行:1) 创建空对象 2) 链到 prototype 3) 执行构造逻辑/字段初始化 4) 返回对象
        const p1 = new Product();
        console.log(p1);
        p1.buy(45);                     // 输出:购买了45件汽车
        p1.addShopcar();                // 输出:将汽车加入购物车!
        
        // 【代码注释】p1.buy === Product.prototype.buy 为 true:方法不随实例复制,节省内存,修改 prototype 影响所有实例
        console.log(p1.buy === Product.prototype.buy);  // true
    </script>
</body>
</html>

【代码注释】可运行示例小结:Class 与 ES5 构造函数+prototype 模式等价;TypeScript class 编译后亦为此结构。注意:箭头函数作类字段会得到实例方法且 this 词法绑定,与普通简写方法不同。

3.2 构造器方法

构造器方法在类实例化时自动执行,用于初始化实例属性。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>构造器方法示例</title>
</head>
<body>
    <script>
        // 【代码注释】显式 constructor 接管实例化逻辑;未写 constructor 时引擎提供默认空构造
        class Product {
            // 【代码注释】类字段声明(name; price;)仅作语义提示,真正赋值在 constructor 中完成
            name;
            price;
            
            // 【代码注释】constructor(name, price) 在 new Product(...) 时自动调用,且必须先于实例方法使用 this
            constructor(name, price) {
                // 【代码注释】this 指向正在构建的实例对象(非 Product 类本身);未 return 对象时默认返回 this
                this.name = name;
                // 【代码注释】入参校验/归一化宜放在 constructor:Math.max 保证业务规则 价格 ≥ 100
                this.price = Math.max(price, 100);
                // 【代码注释】每个实例独立的 num:演示实例字段互不共享(对比 static 类字段)
                this.num = Math.random();
            };
            
            // 【代码注释】buy/addShopcar 仍挂在 prototype,可访问 this.name / this.price
            buy(n) {
                console.log(`购买了${n}件${this.name}`);
            };
            
            addShopcar() {
                console.log('将' + this.name + '加入购物车!');
            };
        }
        
        // 【代码注释】p2 传入 price 99 会被 Math.max 修正为 100 --- 构造器是业务规则入口
        const p1 = new Product('华为p40', 199);
        const p2 = new Product('iphone15', 99);  // 价格会被修正为100
        
        console.log(p1);  // {name: '华为p40', price: 199, num: 0.xxxx}
        console.log(p2);  // {name: 'iphone15', price: 100, num: 0.xxxx}
        
        // 【代码注释】p1.num !== p2.num:实例字段互不影响;修改 p1.name 不会改 p2.name
        console.log(p1.name !== p2.name);  // true
        console.log(p1.num !== p2.num);    // true
    </script>
</body>
</html>

【代码注释】可运行示例小结:子类若定义 constructor 必须super() 再访问 this(见继承节)。公有字段与 constructor 赋值顺序:先字段初始化再执行 constructor 体(规范顺序以引擎为准,避免重复赋值)。

3.3 私有属性

私有属性使用 # 前缀声明,只能在类内部访问。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>私有属性示例</title>
</head>
<body>
    <script>
        // 【代码注释】ES2022 私有字段 #name:编译为 WeakMap 或引擎内部槽,真正无法从实例外部读取
        class Product {
            // 【代码注释】# 前缀须在类体顶部声明;私有名在类外写 p1.#name 为语法错误(非运行时 undefined)
            #name;
            #price;
            #num;
            
            // 【代码注释】constructor 内可通过 this.#name 赋值 --- 仅「声明该私有字段的类」体内合法
            constructor(name, price) {
                // 【代码注释】this.#name / this.#price:外部 p1.name 访问的是公有字段(此处未定义故 undefined)
                this.#name = name;
                this.#price = Math.max(price, 100);
                this.#num = Math.random();
            };
            
            // 【代码注释】buy 内 this.#name 合法:同一类作用域;子类默认**不能**访问父类 # 字段(除非父类暴露 protected 模式)
            buy(n) {
                console.log(`购买了${n}件${this.#name}`);
            };
            
            addShopcar() {
                console.log('将' + this.#name + '加入购物车!');
            };
            
            // 【代码注释】getName() 为受控暴露:封装内部状态,便于日后改存储方式而不破坏 API
            getName() {
                return this.#name;
            }
        }
        
        // 【代码注释】new 之后仅能通过公开方法间接读私有数据 --- 真·封装,优于 _name 命名约定
        const p1 = new Product('华为p40', 199);
        
        // 【代码注释】p1.name / p1.price 为 undefined:没有同名公有字段;勿与 #name 混淆
        console.log(p1.name);     // undefined
        console.log(p1.price);    // undefined
        console.log(p1.num);      // undefined
        
        // 【代码注释】getName() 返回 #name 快照;buy 内拼接 #name 说明私有字段可用于业务逻辑
        p1.buy(12);               // 输出:购买了12件华为p40
        console.log(p1.getName()); // 输出:'华为p40'
        
        // 【代码注释】p1.#name 在类外为 SyntaxError(解析期即失败),比 simply undefined 更严格
        // 【代码注释】类外访问 #name 为语法错误(非运行时 undefined);取消注释无法在解析期通过
        // p1.#name;
    </script>
</body>
</html>

【代码注释】可运行示例小结:私有字段也可用于 #staticField、私有方法 #helper(){}。调试时 DevTools 可能显示但 JS 语义仍不可外部访问。与 WeakMap 私有模式相比,# 语法更简洁且由引擎强制。

私有属性的优势
  1. 封装性:真正的私有性,外部无法访问
  2. 安全性:防止外部直接修改内部状态
  3. 重构友好:内部实现改变不影响外部代码

3.4 静态方法

静态方法属于类本身,而不是实例,使用 static 关键字定义。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>静态方法示例</title>
</head>
<body>
    <script>
        // 【代码注释】static 成员挂在构造函数对象 Product 上,不属于任何实例
        class Product {
            // 【代码注释】static name 与实例 name 无关;Product.name 可能覆盖或共存于不同槽(注意与 Function.name 区分)
            static name = '小乐';
            
            // 【代码注释】getInfo 内 this 默认指向 Product 类;实例方法 this 指向实例
            static getInfo() {
                console.log('我是 Product 类中的静态方法');
            }
            
            // 【代码注释】showInfo 在 prototype 上,须通过实例 p.showInfo() 调用
            showInfo() {
                console.log('这是实例方法');
            }
        }
        
        // 【代码注释】Product.getInfo() / Product.name:工厂方法、工具函数常用 static(如 Array.from)
        console.log(Product.getInfo);  // [Function: getInfo]
        console.log(Product.name);     // '小乐'
        Product.getInfo();             // 输出:我是 Product 类中的静态方法
        
        // 【代码注释】p.getInfo 为 undefined(实例原型链上无此方法);设计意图:工具 API 不污染实例
        const p = new Product();
        // 【代码注释】实例原型链上无 getInfo;取消注释 p.getInfo() 会抛 TypeError
        // p.getInfo();
        
        // 【代码注释】构造函数上无 showInfo;取消注释 Product.showInfo() 会抛 TypeError
        // Product.showInfo();
        
        // 【代码注释】运行期 Product.getPrice = function... 等价于给构造函数加属性;与 class 内 static 效果相同
        Product.getPrice = function() {
            console.log('动态添加的静态方法');
        };
        
        Product.getPrice();  // 输出:动态添加的静态方法
    </script>
</body>
</html>

【代码注释】可运行示例小结:静态块 static {}(ES2022)可在类求值时执行一次初始化。继承时子类通过 super 访问父类静态方法。单例模式常用 static getInstance()。

3.5 Getter 与 Setter 访问器属性

访问器属性get / set 定义:对外像普通属性一样读写(obj.prop),内部自动调用函数,适合校验、派生计算、懒加载等。

javascript 复制代码
// 【代码注释】_price 为内部存储;对外 price 通过 getter/setter 受控访问
class Product {
    #price = 0;

    constructor(name, price) {
        this.name = name;
        this.price = price;  // 触发 setter,走校验逻辑
    }

    // 【代码注释】getter:读取 product.price 时执行,可返回派生值或私有字段
    get price() {
        return this.#price;
    }

    // 【代码注释】setter:赋值 product.price = x 时执行,宜做类型/范围校验
    set price(value) {
        const n = Number(value);
        if (!Number.isFinite(n) || n < 0) {
            throw new RangeError('价格必须为非负有限数');
        }
        this.#price = Math.max(n, 100);
    }

    // 【代码注释】只读计算属性:仅 get 无 set,如 rect.area;赋值在严格模式抛 TypeError
    get label() {
        return `${this.name}(¥${this.#price})`;
    }
}

const p = new Product('手机', 99);
console.log(p.label);   // 手机(¥100)--- 99 经 setter 抬到 100
// p.label = 'x';      // 严格模式 TypeError:无 setter

【代码注释】Class 中 getter/setter 与对象字面量规则相同,等价于 Object.definePropertyget/set 描述符。get/set 挂在 prototype (或实例,若定义为类字段访问器)。常见场景:表单字段校验、依赖其它字段的计算属性(fullName)、首次访问才计算的缓存(懒 getter)。

3.6 类的继承

基本继承语法
javascript 复制代码
// 【代码注释】extends 建立原型链;子类若有 constructor 必须先 super(...)
class ChildClass extends ParentClass {
    constructor(...args) {
        super(...args);  // 【代码注释】先完成父类侧初始化,否则 this 未绑定
        // 【代码注释】此后方可写 this.color 等子类自有字段
    }
}

【代码注释】extends 让子类 prototype 的原型指向父类 prototype,并继承父类静态成员。子类 constructor必须super(...) 才能用 this。同名方法在子类中覆盖 父类,可用 super.method() 调用父类实现。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>类继承示例</title>
</head>
<body>
    <script>
        // 【代码注释】父类 Product:提供 name/price 与 buy 等基础能力;#num 为父类私有字段
        class Product {
            // 【代码注释】公有字段 name/price 与私有 #num 并存,子类实例可继承公有逻辑
            name;
            #num;
            price;
            
            // 【代码注释】父类 constructor 完成价格下限与 #num 初始化
            constructor(name, price) {
                this.name = name;
                this.price = Math.max(price, 100);
                this.#num = Math.random();
            };
            
            // 【代码注释】buy(n) 使用模板字符串与 this.name --- 子类重写后此版本不再被直接调用
            buy(n) {
                console.log(`购买了${n}件${this.name}, 单价是${this.price}`);
            };
            
            addShopcar() {
                console.log('将' + this.name + '加入购物车!');
            };
        }
        
        // 【代码注释】class Phone extends Product:Phone.prototype.__proto__ === Product.prototype
        class Phone extends Product {
            // 【代码注释】类字段 color = 'red' 为子类默认值;constructor 内 this.color = color 会覆盖
            color = 'red';
            
            // 【代码注释】子类必须提供 constructor 才能接收额外参数 color
            constructor(name, price, color) {
                // 【代码注释】super(name, price) 等价于 Product.call(this, name, price),初始化继承的 name/price/#num
                super(name, price);
                // 【代码注释】super 返回后 this 已绑定,可安全写 this.color --- 违反顺序会抛 ReferenceError
                this.color = color;
            }
            
            // 【代码注释】discount 修改继承的 price 字段,演示子类扩展业务行为
            discount(per) {
                this.price *= per;
            }
            
            // 【代码注释】buy() 无参重写:子类同名方法屏蔽父类;p.buy(12) 仍可调但父类形参 n 被忽略
            buy() {
                console.log(`购买了${this.name},价格:${this.price},颜色:${this.color}`);
            }
        }
        
        // 【代码注释】new Phone 先走子类 constructor → super → 父类逻辑 → 子类 color;price 89.9 被父类抬到 100
        const p = new Phone('华为P50', 89.9, 'green');
        
        console.log(p);    // Phone 实例:继承 name/price,自有 color
        p.buy(12);         // 调用子类无参 buy(实参 12 被忽略)
        p.discount(0.8);   // price 100 * 0.8 = 80
        p.buy();           // 输出打折后价格与 color
        
        // 【代码注释】instanceof 沿原型链查找;Phone.prototype instanceof Product 验证继承链接正确
        console.log(p instanceof Phone);      // true
        console.log(p instanceof Product);    // true
        console.log(Phone.prototype instanceof Product);  // true
    </script>
</body>
</html>

【代码注释】可运行示例小结:方法重写 + super.buy() 可组合父类逻辑。注意:箭头函数不能作子类 constructor;深继承层次宜组合优于继承。

3.7 继承内置类

可以继承JavaScript内置的构造函数,如Array、Map、Set等。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>继承内置类示例</title>
</head>
<body>
    <script>
        // 【代码注释】内置类继承:子类须用 super(...args) 调用 Array 构造,否则内部 [[ArrayData]] 未初始化
        class MyArray extends Array {
            #name;
            
            // 【代码注释】constructor(name, ...args) 收集数组元素;name 为自定义元数据
            constructor(name, ...args) {
                // 【代码注释】super(...args) 将 10,20,... 传给 Array,建立 length 与索引;否则 map/push 异常
                super(...args);
                this.#name = name;
            }
            
            // 【代码注释】getName 读私有 #name --- 领域扩展,不改变数组语义
            getName() {
                return this.#name;
            }
            
            // 【代码注释】重写 push 时须 return super.push(...items) 以保持返回值(新 length)与原生一致
            push(...items) {
                console.log(`${this.#name}: 添加了 ${items.length} 个元素`);
                return super.push(...items);
            }
        }
        
        // 【代码注释】ma 既是 Array 实例又是 MyArray;length 含 name 不占索引,元素为 6 个数字
        const ma = new MyArray('xiaole', 10, 20, 30, 40, 50, 60);
        
        console.log(ma);           // MyArray(7) [10, 20, 30, 40, 50, 60]
        console.log(ma.getName()); // 'xiaole'
        console.log(ma.length);    // 7
        console.log(ma.map(x => x * 2));  // [20, 40, 60, 80, 100, 120, 140]
        
        // 【代码注释】使用重写的方法
        ma.push(70, 80);           // 输出:xiaole: 添加了 2 个元素
        console.log(ma.length);    // 9
    </script>
</body>
</html>

【代码注释】可运行示例小结:继承 Array/Map 时注意 Symbol.species 影响 map/slice 返回类型。Array.isArray(ma) 为 true。框架中较少直接继承内置类,多用组合。

3.8 super 关键字

super 有两种用法:作为函数 调用父类构造器;作为对象访问父类原型上的方法(常见于对象字面量方法简写中)。
父类 super 子类 父类 super 子类 #mermaid-svg-bO6C39YWugdq6qsW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bO6C39YWugdq6qsW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bO6C39YWugdq6qsW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bO6C39YWugdq6qsW .error-icon{fill:#552222;}#mermaid-svg-bO6C39YWugdq6qsW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bO6C39YWugdq6qsW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bO6C39YWugdq6qsW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bO6C39YWugdq6qsW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bO6C39YWugdq6qsW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bO6C39YWugdq6qsW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bO6C39YWugdq6qsW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bO6C39YWugdq6qsW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bO6C39YWugdq6qsW .marker.cross{stroke:#333333;}#mermaid-svg-bO6C39YWugdq6qsW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bO6C39YWugdq6qsW p{margin:0;}#mermaid-svg-bO6C39YWugdq6qsW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bO6C39YWugdq6qsW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-bO6C39YWugdq6qsW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bO6C39YWugdq6qsW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-bO6C39YWugdq6qsW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-bO6C39YWugdq6qsW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-bO6C39YWugdq6qsW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-bO6C39YWugdq6qsW .sequenceNumber{fill:white;}#mermaid-svg-bO6C39YWugdq6qsW #sequencenumber{fill:#333;}#mermaid-svg-bO6C39YWugdq6qsW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-bO6C39YWugdq6qsW .messageText{fill:#333;stroke:none;}#mermaid-svg-bO6C39YWugdq6qsW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bO6C39YWugdq6qsW .labelText,#mermaid-svg-bO6C39YWugdq6qsW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-bO6C39YWugdq6qsW .loopText,#mermaid-svg-bO6C39YWugdq6qsW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-bO6C39YWugdq6qsW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bO6C39YWugdq6qsW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-bO6C39YWugdq6qsW .noteText,#mermaid-svg-bO6C39YWugdq6qsW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-bO6C39YWugdq6qsW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bO6C39YWugdq6qsW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bO6C39YWugdq6qsW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bO6C39YWugdq6qsW .actorPopupMenu{position:absolute;}#mermaid-svg-bO6C39YWugdq6qsW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-bO6C39YWugdq6qsW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bO6C39YWugdq6qsW .actor-man circle,#mermaid-svg-bO6C39YWugdq6qsW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-bO6C39YWugdq6qsW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} constructor 中 super(args) 执行父类 constructor 初始化继承的属性 再设置子类自有属性

【代码注释】时序图要点:派生类 new 时引擎先创建 this,再执行 super() 完成父类侧初始化,最后执行子类 constructor 剩余代码。未 super() 前访问 this → ReferenceError。

使用位置 形式 含义
子类 constructor super(arg1, arg2) 调用父类构造器,必须 在访问 this 之前
子类实例方法 super.method() 调用父类原型上的 method
对象方法简写 super.xxx super 指向当前对象的原型
javascript 复制代码
// 【代码注释】super() 调用父类 constructor;super.speak() 调用父类 prototype 上的方法(非构造函数)
class Animal {
    speak() { return '...'; }
}
class Dog extends Animal {
    constructor(name) {
        super(); // 子类 constructor 内必须先 super,再 this
        this.name = name;
    }
    speak() {
        return super.speak() + this.name;  // 重写时复用父类实现
    }
}

【代码注释】super() 在 constructor 中调用父类构造;super.speak() 在实例方法中取父类 prototype 上的方法。对象字面量 { fn() { super.x } } 中 super 指向原型对象(非类继承)。JavaScript 单继承,多父用 mixin 或组合。

继承规则归纳
  1. 子类中同名 属性或方法会覆盖父类成员。
  2. 子类重写 constructor 时,必须先 super(...),且 super 须写在构造器最前面
  3. 子类实例的原型链:实例 → 子类.prototype → 父类实例(由 super 逻辑建立)→ 父类.prototype

3.9 Mixin 模式:弥补单继承的局限

JavaScript 只支持单继承,但实际开发中常需要组合多个行为。Mixin 通过函数返回匿名类,将能力「混入」目标类。

javascript 复制代码
// 【代码注释】Mixin 工厂函数:接收 Base 类,返回扩展后的匿名类,可链式叠加
const Serializable = (Base) => class extends Base {
    // 【代码注释】serialize 将实例转为 JSON 字符串,不依赖任何父类细节
    serialize() {
        return JSON.stringify(this);
    }
    static deserialize(json) {
        return Object.assign(new this(), JSON.parse(json));
    }
};

const Validatable = (Base) => class extends Base {
    // 【代码注释】validate 读取子类定义的 rules 对象;每条规则为 (value) => errorMsg | null
    validate() {
        const rules = this.constructor.rules ?? {};
        const errors = [];
        for (const [field, rule] of Object.entries(rules)) {
            const error = rule(this[field]);
            if (error) errors.push(`${field}: ${error}`);
        }
        return errors;
    }
};

const Timestampable = (Base) => class extends Base {
    constructor(...args) {
        super(...args);
        // 【代码注释】createdAt 在混入层初始化,所有混入该 Mixin 的类都自动获得时间戳
        this.createdAt = new Date().toISOString();
    }
};

// 【代码注释】链式混入:User extends Timestampable(Validatable(Serializable(class {})))
// 原型链:User → Timestampable匿名类 → Validatable匿名类 → Serializable匿名类 → 空基类
class User extends Timestampable(Validatable(Serializable(class {}))) {
    static rules = {
        name: (v) => (!v ? '姓名不能为空' : null),
        age:  (v) => (v < 0 || v > 150 ? '年龄不合法' : null),
    };

    constructor(name, age) {
        super();
        this.name = name;
        this.age = age;
    }
}

const user = new User('Alice', 25);
console.log(user.validate());   // [] --- 无错误
console.log(user.serialize());  // '{"name":"Alice","age":25,"createdAt":"..."}'
console.log(user.createdAt);    // ISO 时间字符串

const badUser = new User('', -1);
console.log(badUser.validate());  // ['name: 姓名不能为空', 'age: 年龄不合法']

【代码注释】Mixin 模式核心优势:能力正交、按需组合 ,比深层继承更灵活。Vue 2 的 mixins 选项、React 早期的 HOC(Higher-Order Component)本质相同。注意:多个 Mixin 定义同名方法时,最后混入的优先(原型链覆盖顺序);TypeScript 需配合 interface 声明类型约束。

3.10 经典应用场景

  1. 组件开发:创建可复用的 UI 组件类
  2. 数据模型:定义数据模型和业务逻辑
  3. 工具类:创建功能性的工具类库
  4. 扩展内置类型 :如 class MyArray extends Array 在保留数组 API 的同时增加领域方法
  5. Mixin 组合:用函数式 Mixin 替代多重继承,组合序列化、验证、日志等横切关注点

3.11 市面应用实例

  • React:使用class定义组件类
  • Node.js:Stream、EventEmitter等核心模块
  • TypeScript:增强的class语法支持

4. Set与Map数据结构

ES6引入了Set和Map两种新的数据结构,为JavaScript提供了更强大的数据存储和操作能力。

4.1 Set数据结构

Set是值的集合,其中的值唯一且不重复。

Set的核心特性

#mermaid-svg-KyZx9oXceoDhYmwH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KyZx9oXceoDhYmwH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KyZx9oXceoDhYmwH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KyZx9oXceoDhYmwH .error-icon{fill:#552222;}#mermaid-svg-KyZx9oXceoDhYmwH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KyZx9oXceoDhYmwH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KyZx9oXceoDhYmwH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KyZx9oXceoDhYmwH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KyZx9oXceoDhYmwH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KyZx9oXceoDhYmwH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KyZx9oXceoDhYmwH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KyZx9oXceoDhYmwH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KyZx9oXceoDhYmwH .marker.cross{stroke:#333333;}#mermaid-svg-KyZx9oXceoDhYmwH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KyZx9oXceoDhYmwH p{margin:0;}#mermaid-svg-KyZx9oXceoDhYmwH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KyZx9oXceoDhYmwH .cluster-label text{fill:#333;}#mermaid-svg-KyZx9oXceoDhYmwH .cluster-label span{color:#333;}#mermaid-svg-KyZx9oXceoDhYmwH .cluster-label span p{background-color:transparent;}#mermaid-svg-KyZx9oXceoDhYmwH .label text,#mermaid-svg-KyZx9oXceoDhYmwH span{fill:#333;color:#333;}#mermaid-svg-KyZx9oXceoDhYmwH .node rect,#mermaid-svg-KyZx9oXceoDhYmwH .node circle,#mermaid-svg-KyZx9oXceoDhYmwH .node ellipse,#mermaid-svg-KyZx9oXceoDhYmwH .node polygon,#mermaid-svg-KyZx9oXceoDhYmwH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KyZx9oXceoDhYmwH .rough-node .label text,#mermaid-svg-KyZx9oXceoDhYmwH .node .label text,#mermaid-svg-KyZx9oXceoDhYmwH .image-shape .label,#mermaid-svg-KyZx9oXceoDhYmwH .icon-shape .label{text-anchor:middle;}#mermaid-svg-KyZx9oXceoDhYmwH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KyZx9oXceoDhYmwH .rough-node .label,#mermaid-svg-KyZx9oXceoDhYmwH .node .label,#mermaid-svg-KyZx9oXceoDhYmwH .image-shape .label,#mermaid-svg-KyZx9oXceoDhYmwH .icon-shape .label{text-align:center;}#mermaid-svg-KyZx9oXceoDhYmwH .node.clickable{cursor:pointer;}#mermaid-svg-KyZx9oXceoDhYmwH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KyZx9oXceoDhYmwH .arrowheadPath{fill:#333333;}#mermaid-svg-KyZx9oXceoDhYmwH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KyZx9oXceoDhYmwH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KyZx9oXceoDhYmwH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KyZx9oXceoDhYmwH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KyZx9oXceoDhYmwH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KyZx9oXceoDhYmwH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KyZx9oXceoDhYmwH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KyZx9oXceoDhYmwH .cluster text{fill:#333;}#mermaid-svg-KyZx9oXceoDhYmwH .cluster span{color:#333;}#mermaid-svg-KyZx9oXceoDhYmwH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KyZx9oXceoDhYmwH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KyZx9oXceoDhYmwH rect.text{fill:none;stroke-width:0;}#mermaid-svg-KyZx9oXceoDhYmwH .icon-shape,#mermaid-svg-KyZx9oXceoDhYmwH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KyZx9oXceoDhYmwH .icon-shape p,#mermaid-svg-KyZx9oXceoDhYmwH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KyZx9oXceoDhYmwH .icon-shape .label rect,#mermaid-svg-KyZx9oXceoDhYmwH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KyZx9oXceoDhYmwH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KyZx9oXceoDhYmwH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KyZx9oXceoDhYmwH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Set特性
唯一性
无序性
可遍历
自动去重
无索引访问
支持for...of

【代码注释】Set 用 SameValueZero 判等(NaNNaN 视为同一成员)。无键forEach 回调 (value, value) 两参相同。对象/数组成员按引用 去重,展开 new Set([obj1, obj2]) 去重对象数组无效,须自定义 key。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Set数据结构示例</title>
</head>
<body>
    <script>
        // 【代码注释】空 Set、可迭代构造、字符串构造:字符串按 Unicode 码元拆分('Hello World' → 8 个不重复字符)
        const s1 = new Set();
        const s2 = new Set([
            100, 200, 200, 200, 300, 400, 400, 500,
            { name: 'xiaole' }, { name: 'xiaole' }  // 两个不同对象引用,不会去重
        ]);
        const s3 = new Set('Hello World');
        
        console.log(s1);  // Set(0) {}
        console.log(s2);  // 原始值去重,对象按引用保留多个
        console.log(s3);  // Set(8) {'H', 'e', 'l', 'o', ' ', 'W', 'r', 'd'}
        
        // 【代码注释】add 返回 this 可链式;重复原始值被忽略,size 不变
        s2.add(250);
        s2.add(100);  // 重复值不会添加
        
        // 【代码注释】delete(200) 成功;delete({name}) 失败 --- 须持有**同一引用**才能删对象成员
        s2.delete(200);
        s2.delete({name: 'xiaole'});  // 对象引用不同,删除无效
        
        // 【代码注释】s2.clear() 一键清空 Set;示例注释掉以保留后续 has/forEach 演示数据
        
        // 【代码注释】has 用 SameValueZero;新对象 {name:'xiaole'} 与 Set 内对象引用不同 → false
        console.log(s2.has(250));    // true
        console.log(s2.has(2501));   // false
        console.log(s2.has({name: 'xiaole'}));  // false,对象引用不同
        
        // 【代码注释】Set.forEach 第二参数 index 实际仍是 value(规范设计),勿当成数组下标
        s2.forEach((item, index) => {
            console.log(item);       // 注意:Set的forEach回调参数中,两个参数都是值
        });
        
        console.log('Set长度:', s2.size);
        
        // 【代码注释】[...new Set(arr)] 与 Array.from(new Set(arr)) 等价,O(n) 且保持首次出现顺序
        const arr = ['司马姥姥', '欧阳姥姥', '司马姥姥', '欧阳姥姥', '东方姥姥'];
        console.log('原数组:', arr);
        
        // 【代码注释】方法一:[...new Set(arr)] --- 最简写法,保持首次出现顺序,O(n)
        const newArr1 = [...new Set(arr)];
        console.log('去重后(扩展运算符):', newArr1);
        
        // 【代码注释】方法二:Array.from(new Set(arr)) --- 语义相同,适合需要传 mapFn 的场景
        const newArr2 = Array.from(new Set(arr));
        console.log('去重后(Array.from):', newArr2);
        
        // 【代码注释】keys/values 在 Set 中相同;entries 产生 [v,v] 对,供 Map 风格解构
        for (let item of s2) {
            console.log('for...of遍历:', item);
        }
        
        // 【代码注释】Set的其他方法
        console.log('keys():', s2.keys());    // Set Iterator
        console.log('values():', s2.values()); // Set Iterator
        console.log('entries():', s2.entries()); // Set Iterator,返回[value, value]
    </script>
</body>
</html>

【代码注释】可运行示例小结:集合运算 new Set([...a].filter(x => b.has(x))) 求交集。React useEffect 依赖数组常用 [...new Set(deps)] 去重。WeakSet 见下节。

Set的主要方法和属性
方法/属性 描述 返回值
add(value) 添加值 Set对象本身
delete(value) 删除值 布尔值,表示是否成功
has(value) 检查值是否存在 布尔值
clear() 清除所有值 undefined
size 返回成员总数 数字
values() 返回值的遍历器 Iterator
keys() 返回键的遍历器(等同于values) Iterator
entries() 返回键值对的遍历器 Iterator
forEach() 使用回调函数遍历每个成员 undefined

4.2 WeakSet数据结构

WeakSet与Set类似,但有几个重要区别:

  1. 只能存储对象类型
  2. 对象是弱引用,不计入垃圾回收机制
  3. 不可遍历
完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WeakSet示例</title>
</head>
<body>
    <script>
        // 【代码注释】WeakSet 构造参数须为对象;原始值会被忽略或抛错(视引擎与包装对象)
        const msg = new String('Hello');
        const ws = new WeakSet([
            new Number(100),
            msg,
            [10, 20, 30],
            {name: '小乐'},
            msg  // 重复的对象引用只存储一次
        ]);
        
        console.log(ws);  // WeakSet {Number, String, Array, Object}
        
        // 【代码注释】ws.add(document.body) 标记 DOM 节点,节点移除且无其他引用时可被 GC
        ws.add(document.body);
        
        // 【代码注释】ws.delete(msg) 删除的是**当时存入的** msg 引用
        ws.delete(msg);
        
        // 【代码注释】新对象 {name} 与 WeakSet 内对象引用不同;document.body 同一引用 → has true
        console.log(ws.has({name: '小乐'}));  // false,对象引用不同
        console.log(ws.has(document.body));   // true,相同的DOM引用
        
        // 【代码注释】弱引用:WeakSet 不阻止 GC 回收对象;对象存活与否无法通过 size 观察(无 size)
        let obj = {data: 'test'};
        ws.add(obj);
        console.log(ws.has(obj));  // true
        
        // 【代码注释】obj = null 后,WeakSet 中对应条目自动消失(不可遍历故无法 console 验证)
        obj = null;
        // 【代码注释】obj=null 后 WeakSet 条目随 GC 消失,但无 size/forEach,无法在控制台直接「看到」删除
        
        // 【代码注释】用 WeakSet 记录「已处理过的节点」,节点从 DOM 移除后自动释放,防内存泄漏
        const nodes = new WeakSet();
        const div1 = document.createElement('div');
        const div2 = document.createElement('div');
        
        nodes.add(div1);
        nodes.add(div2);
        
        console.log(nodes.has(div1));  // true
        
        // 【代码注释】div1.remove() 后若无其他强引用,WeakSet 不再持有该节点 --- 适合事件委托标记
        // 【代码注释】div1.remove() 后若无其它强引用,WeakSet 中该节点条目可被 GC(弱引用不阻止回收)
    </script>
</body>
</html>

【代码注释】可运行示例小结:WeakSet 无 size/clear/forEach。与 Set 选型:需遍历、计数、原始值 → Set;仅需「是否见过该对象」且防泄漏 → WeakSet。

WeakSet的应用场景
  1. DOM节点存储:存储DOM节点而不影响垃圾回收
  2. 对象标记:标记对象而不阻止其被垃圾回收
  3. 私有数据存储:为对象关联私有数据

4.3 Map数据结构

Map是键值对的集合,键可以是任意类型的数据。

Map vs Object

#mermaid-svg-1vGRtujirZ1gk6jh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1vGRtujirZ1gk6jh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1vGRtujirZ1gk6jh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1vGRtujirZ1gk6jh .error-icon{fill:#552222;}#mermaid-svg-1vGRtujirZ1gk6jh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1vGRtujirZ1gk6jh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1vGRtujirZ1gk6jh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1vGRtujirZ1gk6jh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1vGRtujirZ1gk6jh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1vGRtujirZ1gk6jh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1vGRtujirZ1gk6jh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1vGRtujirZ1gk6jh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1vGRtujirZ1gk6jh .marker.cross{stroke:#333333;}#mermaid-svg-1vGRtujirZ1gk6jh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1vGRtujirZ1gk6jh p{margin:0;}#mermaid-svg-1vGRtujirZ1gk6jh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1vGRtujirZ1gk6jh .cluster-label text{fill:#333;}#mermaid-svg-1vGRtujirZ1gk6jh .cluster-label span{color:#333;}#mermaid-svg-1vGRtujirZ1gk6jh .cluster-label span p{background-color:transparent;}#mermaid-svg-1vGRtujirZ1gk6jh .label text,#mermaid-svg-1vGRtujirZ1gk6jh span{fill:#333;color:#333;}#mermaid-svg-1vGRtujirZ1gk6jh .node rect,#mermaid-svg-1vGRtujirZ1gk6jh .node circle,#mermaid-svg-1vGRtujirZ1gk6jh .node ellipse,#mermaid-svg-1vGRtujirZ1gk6jh .node polygon,#mermaid-svg-1vGRtujirZ1gk6jh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1vGRtujirZ1gk6jh .rough-node .label text,#mermaid-svg-1vGRtujirZ1gk6jh .node .label text,#mermaid-svg-1vGRtujirZ1gk6jh .image-shape .label,#mermaid-svg-1vGRtujirZ1gk6jh .icon-shape .label{text-anchor:middle;}#mermaid-svg-1vGRtujirZ1gk6jh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1vGRtujirZ1gk6jh .rough-node .label,#mermaid-svg-1vGRtujirZ1gk6jh .node .label,#mermaid-svg-1vGRtujirZ1gk6jh .image-shape .label,#mermaid-svg-1vGRtujirZ1gk6jh .icon-shape .label{text-align:center;}#mermaid-svg-1vGRtujirZ1gk6jh .node.clickable{cursor:pointer;}#mermaid-svg-1vGRtujirZ1gk6jh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1vGRtujirZ1gk6jh .arrowheadPath{fill:#333333;}#mermaid-svg-1vGRtujirZ1gk6jh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1vGRtujirZ1gk6jh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1vGRtujirZ1gk6jh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1vGRtujirZ1gk6jh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1vGRtujirZ1gk6jh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1vGRtujirZ1gk6jh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1vGRtujirZ1gk6jh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1vGRtujirZ1gk6jh .cluster text{fill:#333;}#mermaid-svg-1vGRtujirZ1gk6jh .cluster span{color:#333;}#mermaid-svg-1vGRtujirZ1gk6jh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1vGRtujirZ1gk6jh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1vGRtujirZ1gk6jh rect.text{fill:none;stroke-width:0;}#mermaid-svg-1vGRtujirZ1gk6jh .icon-shape,#mermaid-svg-1vGRtujirZ1gk6jh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1vGRtujirZ1gk6jh .icon-shape p,#mermaid-svg-1vGRtujirZ1gk6jh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1vGRtujirZ1gk6jh .icon-shape .label rect,#mermaid-svg-1vGRtujirZ1gk6jh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1vGRtujirZ1gk6jh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1vGRtujirZ1gk6jh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1vGRtujirZ1gk6jh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据存储
Object
Map
键只能是字符串/Symbol
有原型链
键可以是任意类型
无原型链
有序

【代码注释】Map vs Object:Map 任意类型键插入顺序size 属性、无原型污染Object.create(null) 可部分模拟)。Object 更适合 JSON 序列化、字面量配置。频繁增删键值对时 Map 性能常更稳。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Map数据结构示例</title>
</head>
<body>
    <script>
        // 【代码注释】数组/对象/Set 作键时,后续 get/set 必须用**同一引用**(arr 变量,非新数组字面量)
        const arr = [10, 20, 30, 40];
        const user = {name: '小乐'};
        const s = new Set(arr);
        
        // 【代码注释】构造器接收 [key, value] 可迭代对;重复键(同引用)后者覆盖前者
        const m = new Map([
            [arr, '数组作为键'],
            [user, '对象作为键'],
            [s, ['司马姥姥', '欧阳姥姥']],
            ['address', '字符串作为键'],
            [100, '数字作为键']
        ]);
        
        console.log(m);
        
        // 【代码注释】m.set(arr, ...) 更新同一键;m.set([10,20], 350) 是新键,与 arr 引用不同
        m.set(arr, '更新后的数组值');
        m.set([10, 20], 350);  // 新数组,不同的键
        
        // 【代码注释】m.get([10, 20]) 为 undefined:字面量每次新建引用;工程上应用 Map 缓存对象元数据时存变量引用
        console.log(m.get(arr));       // '更新后的数组值'
        console.log(m.get([10, 20]));  // undefined,数组引用不同
        
        // 【代码注释】has 同样按引用比较键;Object.is 规则
        console.log(m.has(arr));       // true
        console.log(m.has([10, 20]));  // false
        
        // 【代码注释】wm.delete(user) 显式删除;弱引用不保证立即回收时机
        // 【代码注释】m.delete(arr) 按引用删键;m.clear() 清空全部条目(示例中注释掉以免破坏后续演示)
        // 【代码注释】m.delete(arr) 按引用删键;m.clear() 清空全部(示例注释掉以保留后续遍历数据)
        // m.delete(arr);
        // m.clear();
        
        // 【代码注释】keys/values/entries 返回 Map Iterator;默认 for...of 遍历 entries
        console.log('keys:', m.keys());     // Map Iterator
        console.log('values:', m.values()); // Map Iterator
        console.log('entries:', m.entries()); // Map Iterator
        
        // 【代码注释】解构 [key, value] 按插入顺序;键为对象时 key 显示为 [object Object] 需自行标识
        for (let [key, value] of m) {
            console.log(`${key} => ${value}`);
        }
        
        // 【代码注释】Map.forEach 参数顺序为 (value, key),与数组 forEach(value, index) 不同,易写反
        m.forEach((value, key) => {
            console.log(`${key} => ${value}`);
        });
        
        // 【代码注释】m.size O(1) 读取;Object 需 Object.keys(obj).length
        console.log('Map大小:', m.size);
        
        // 【代码注释】Object.entries → Map 保留键顺序(字符串键);Map → Object 键自动 toString
        const obj = {name: '张三', age: 25};
        const objMap = new Map(Object.entries(obj));
        console.log('Object转Map:', objMap);
        
        const newObj = Object.fromEntries(objMap);
        console.log('Map转Object:', newObj);
    </script>
</body>
</html>

【代码注释】可运行示例小结:Vue3 reactive 用 Map 存依赖;LRU 缓存用 Map + 链表。map.get(key) ?? default 取缺省。WeakMap 键必须为对象。

4.4 WeakMap数据结构

WeakMap与Map类似,但键必须是对象,且键是弱引用。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WeakMap示例</title>
</head>
<body>
    <script>
        // 【代码注释】WeakMap 键必须是对象(或继承自 Object 的类型);原始值作键无效
        const arr = [10, 20, 30, 40];
        const user = {name: '小乐'};
        const s = new Set(arr);
        
        // 【代码注释】初始化列表中的 [{}] 每次都是新对象,彼此独立
        const wm = new WeakMap([
            [arr, '数组关联数据'],
            [user, '对象关联数据'],
            [s, ['嵌套数据']],
            [{}, '匿名对象'],
            [new Number(100), '包装对象']
        ]);
        
        console.log(wm);
        
        // 【代码注释】wm.set(arr, ...) 更新与 arr 同一引用关联的值
        wm.set(arr, '更新的值');
        console.log(wm.get(arr));  // '更新的值'
        
        // 【代码注释】delete(user) 后 has(user) 为 false;键对象被 GC 后条目自动移除
        console.log(wm.has(arr));  // true
        
        // 【代码注释】删除键值对
        wm.delete(user);
        console.log(wm.has(user));  // false
        
        // 【代码注释】模块级 privateData WeakMap:每个实例 this 作键存秘密,外部无法枚举所有键
        const privateData = new WeakMap();
        
        class MyClass {
            constructor() {
                privateData.set(this, {
                    secret: 'private data',
                    created: Date.now()
                });
            }
            
            getSecret() {
                return privateData.get(this)?.secret;
            }
        }
        
        const instance = new MyClass();
        console.log(instance.getSecret());  // 'private data'
        
        // 【代码注释】若导出 privateData,持有模块引用者仍可 get(instance) --- 真隔离需闭包或 # 私有字段
        console.log(privateData.get(instance));  // 可以访问,但这是故意的
        // 【代码注释】若模块导出 privateData,同模块外仍可通过 get(instance) 读取 --- 真隔离用 # 或闭包不导出 WeakMap
    </script>
</body>
</html>

【代码注释】可运行示例小结:WeakMap 适合 DOM 节点→元数据、实例→私有状态。无 size/keys。与 # 私有字段相比,WeakMap 可跨模块在闭包外保持弱关联。

4.5 经典应用场景

Set 集合运算完整示例
javascript 复制代码
// 【代码注释】Set 集合运算三件套:并集、交集、差集,时间复杂度均为 O(n)
const A = new Set([1, 2, 3, 4, 5]);
const B = new Set([3, 4, 5, 6, 7]);

// 【代码注释】并集:展开两个 Set 传入新 Set,重复元素自动去重
const union = new Set([...A, ...B]);
console.log('并集:', [...union]);  // [1, 2, 3, 4, 5, 6, 7]

// 【代码注释】交集:保留 A 中同时存在于 B 的元素(has 查询为 O(1))
const intersection = new Set([...A].filter(x => B.has(x)));
console.log('交集:', [...intersection]);  // [3, 4, 5]

// 【代码注释】差集(A - B):保留 A 中不在 B 中的元素
const difference = new Set([...A].filter(x => !B.has(x)));
console.log('差集 A-B:', [...difference]);  // [1, 2]

// 【代码注释】对称差集:两个集合各自独有元素的合集(等于 (A-B) ∪ (B-A))
const symmetricDiff = new Set([
    ...[...A].filter(x => !B.has(x)),
    ...[...B].filter(x => !A.has(x))
]);
console.log('对称差集:', [...symmetricDiff]);  // [1, 2, 6, 7]

// 【代码注释】子集判断:A 中所有元素是否都在 B 中(every 短路求值)
const isSubset = (setA, setB) => [...setA].every(x => setB.has(x));
console.log('A 是 B 的子集?', isSubset(new Set([3, 4]), B));  // true

// 【代码注释】实战:权限系统中判断用户权限是否满足接口所需权限
const userPermissions = new Set(['read', 'write', 'comment']);
const requiredPermissions = new Set(['read', 'write']);
const hasAccess = isSubset(requiredPermissions, userPermissions);
console.log('用户有访问权限:', hasAccess);  // true

【代码注释】集合运算最佳实践:filter + has 组合时间复杂度 O(n),优于双层 indexOf 的 O(n²)。当集合较大时性能差异显著。封装为工具函数时建议返回新 Set 而非修改原集合,保持不可变性。

Set应用场景
  1. 数组去重:最经典的应用场景
  2. 集合运算:交集、并集、差集等
  3. 标签系统:确保标签不重复
Map 经典案例:LRU Cache(最近最少使用缓存)

LRU Cache 是高频面试题(LeetCode 146),Map 的插入顺序特性使其天然适合实现:

javascript 复制代码
// 【代码注释】Map 按插入顺序迭代,keys().next() 总拿到「最旧」的键 --- LRU 的关键特性
class LRUCache {
    #capacity;
    #cache;  // Map 维护访问顺序:最近访问的在末尾,最久未访问的在开头

    constructor(capacity) {
        this.#capacity = capacity;
        this.#cache = new Map();
    }

    get(key) {
        if (!this.#cache.has(key)) return -1;
        // 【代码注释】删除再重新插入,将该键移动到 Map 末尾,表示「最近使用」
        const value = this.#cache.get(key);
        this.#cache.delete(key);
        this.#cache.set(key, value);
        return value;
    }

    put(key, value) {
        if (this.#cache.has(key)) {
            this.#cache.delete(key);  // 移除旧位置
        } else if (this.#cache.size >= this.#capacity) {
            // 【代码注释】Map.keys().next().value 取出「最旧」的键(插入最早)并淘汰
            const oldestKey = this.#cache.keys().next().value;
            this.#cache.delete(oldestKey);
        }
        this.#cache.set(key, value);  // 插入到末尾,标记为「最新使用」
    }

    // 【代码注释】调试辅助:展示当前缓存状态(最左为最旧,最右为最新)
    toString() {
        return [...this.#cache.entries()]
            .map(([k, v]) => `${k}:${v}`)
            .join(' → ');
    }
}

const lru = new LRUCache(3);
lru.put('a', 1);
lru.put('b', 2);
lru.put('c', 3);
console.log(lru.toString());  // a:1 → b:2 → c:3

lru.get('a');                 // 访问 a,a 移至末尾
console.log(lru.toString());  // b:2 → c:3 → a:1

lru.put('d', 4);              // 容量满,淘汰最旧的 b
console.log(lru.toString());  // c:3 → a:1 → d:4
console.log(lru.get('b'));    // -1,b 已被淘汰

【代码注释】LRU Cache 核心思路:Map 的插入顺序天然代表访问时间线;delete + set 将键移至末尾表示最近使用;keys().next() 取最旧键。时间复杂度 O(1)(get/put 均为),空间 O(capacity)。真实生产常结合 node-lru-cache 或 Redis TTL 策略。

Map应用场景
  1. 缓存系统:键值对缓存
  2. 配置管理:复杂配置的存储
  3. 数据映射:复杂数据结构的关联

4.6 市面应用实例

  • React:使用Set和Map管理状态和副作用
  • Vue:响应式系统的内部实现
  • Lodash:大量使用Map和Set进行数据处理

5. 遍历器与可遍历对象

ES6引入的迭代协议为JavaScript提供了统一的遍历机制。

5.1 遍历器对象Iterator

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。

Iterator的核心概念

Data Iterator Client Data Iterator Client #mermaid-svg-4NkmDRL9FVbvuVpz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4NkmDRL9FVbvuVpz .error-icon{fill:#552222;}#mermaid-svg-4NkmDRL9FVbvuVpz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4NkmDRL9FVbvuVpz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4NkmDRL9FVbvuVpz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4NkmDRL9FVbvuVpz .marker.cross{stroke:#333333;}#mermaid-svg-4NkmDRL9FVbvuVpz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4NkmDRL9FVbvuVpz p{margin:0;}#mermaid-svg-4NkmDRL9FVbvuVpz .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4NkmDRL9FVbvuVpz text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-4NkmDRL9FVbvuVpz .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4NkmDRL9FVbvuVpz .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-4NkmDRL9FVbvuVpz .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-4NkmDRL9FVbvuVpz .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-4NkmDRL9FVbvuVpz #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-4NkmDRL9FVbvuVpz .sequenceNumber{fill:white;}#mermaid-svg-4NkmDRL9FVbvuVpz #sequencenumber{fill:#333;}#mermaid-svg-4NkmDRL9FVbvuVpz #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-4NkmDRL9FVbvuVpz .messageText{fill:#333;stroke:none;}#mermaid-svg-4NkmDRL9FVbvuVpz .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4NkmDRL9FVbvuVpz .labelText,#mermaid-svg-4NkmDRL9FVbvuVpz .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-4NkmDRL9FVbvuVpz .loopText,#mermaid-svg-4NkmDRL9FVbvuVpz .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-4NkmDRL9FVbvuVpz .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4NkmDRL9FVbvuVpz .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-4NkmDRL9FVbvuVpz .noteText,#mermaid-svg-4NkmDRL9FVbvuVpz .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-4NkmDRL9FVbvuVpz .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4NkmDRL9FVbvuVpz .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4NkmDRL9FVbvuVpz .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4NkmDRL9FVbvuVpz .actorPopupMenu{position:absolute;}#mermaid-svg-4NkmDRL9FVbvuVpz .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-4NkmDRL9FVbvuVpz .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4NkmDRL9FVbvuVpz .actor-man circle,#mermaid-svg-4NkmDRL9FVbvuVpz line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-4NkmDRL9FVbvuVpz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 指针移动到下一个位置 获取Iterator 调用next() 获取当前数据 返回数据 返回{value, done} 调用next() 返回{value, done}

【代码注释】Iterator 协议 :实现 next() 返回 { value, done }done: truevalue 常为 undefinedreturn 的值会成为最后一次 next().value)。遍历器一次性 ,指针不回退;重新遍历须 arr[Symbol.iterator]() 新建。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>遍历器对象示例</title>
</head>
<body>
    <script>
        // 【代码注释】数组原生部署 Symbol.iterator,values()/keys()/entries() 返回不同 Iterator
        const arr = [100, 200, 300, 400, 500, 600, 700];
        
        // 【代码注释】arr.values() 等价于 arr[Symbol.iterator](),按索引顺序产出元素值
        const valIter = arr.values();
        console.log('遍历器对象:', valIter);
        
        // 【代码注释】连续 next() 直至 done:true;最后一次 value 为 undefined,表示耗尽
        console.log(valIter.next());  // {value: 100, done: false}
        console.log(valIter.next());  // {value: 200, done: false}
        console.log(valIter.next());  // {value: 300, done: false}
        console.log(valIter.next());  // {value: 400, done: false}
        console.log(valIter.next());  // {value: 500, done: false}
        console.log(valIter.next());  // {value: 600, done: false}
        console.log(valIter.next());  // {value: 700, done: false}
        console.log(valIter.next());  // {value: undefined, done: true}
        
        // 【代码注释】keys() 产出索引 0,1,2,... --- 稀疏数组会跳过空槽(与 forEach 行为相关)
        const keyIter = arr.keys();
        console.log('---keys遍历器---');
        
        // 【代码注释】手写 while+next 等价于 for...of 底层逻辑;需在 done 时 break
        while (true) {
            let data = keyIter.next();
            if (data.done) {
                break;  // 遍历结束
            }
            console.log('索引:', data.value);
        }
        
        // 【代码注释】entries() 产出 [index, value] 二元数组,适合需要下标的场景
        const kvIter = arr.entries();
        console.log('---entries遍历器---');
        
        for (let v of kvIter) {
            console.log('键值对:', v);  // [索引, 值]
        }
        
        // 【代码注释】规范:Iterator 本身也是 Iterable,其 [Symbol.iterator]() 返回 this
        console.log('---遍历器特点---');
        
        // 【代码注释】特点 1:Iterator 部署了 [Symbol.iterator],故可用 for...of 消费遍历器本身
        const iter = arr[Symbol.iterator]();
        console.log('遍历器是可遍历的:', typeof iter[Symbol.iterator] === 'function');
        
        // 【代码注释】特点 2:iter[Symbol.iterator]() === iter,for...of 不会无限新建遍历器
        console.log(iter[Symbol.iterator]() === iter);  // true
        
        // 【代码注释】特点 3:遍历状态一次性;firstIter 消耗后须 arr.values() 新建才能从头遍历
        const firstIter = arr.values();
        console.log('第一次:', firstIter.next());
        console.log('第二次:', firstIter.next());
        const secondIter = arr.values();  // 新 Iterator,指针重置到首元素
        console.log('新遍历器:', secondIter.next());  // 重新从 100 开始
    </script>
</body>
</html>

【代码注释】可运行示例小结:for...of 内部反复调用 next()。Generator 返回的对象同时满足 Iterator+Iterable。不要并发共享同一 Iterator(竞态)。

5.2 可遍历对象Iterable

Iterable是部署了Iterator接口的对象,可以通过for...of循环遍历。

内置的可遍历对象
javascript 复制代码
// 【代码注释】以下类型在 ES6+ 中默认实现 Iterable(部署 Symbol.iterator),可直接 for...of / 展开
Array的实例      // 按索引顺序迭代元素值
Set的实例        // 按插入顺序迭代成员值(无重复)
Map的实例        // 按插入顺序迭代 [key, value] 二元组
字符串           // 按 UTF-16 码元迭代(含代理对时需注意 emoji 长度)
arguments        // 按实参顺序迭代(严格模式与箭头函数中慎用)
NodeList         // DOM 查询结果,类数组且可迭代
HTMLCollection   // 如 form.elements,live 集合,迭代时 DOM 变化可能影响结果
TypedArray       // 二进制视图,按索引迭代数值

【代码注释】Iterable 协议obj[Symbol.iterator] 为函数,调用后返回 Iterator。内置:Array、String、Set、Map、arguments、NodeList、TypedArray 等。普通 {} 默认不可迭代,须自行实现或 Object.keys

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>可遍历对象示例</title>
</head>
<body>
    <script>
        // 【代码注释】字符串按码元迭代;Set 去重后仍保持插入顺序可 for...of
        const arr = [100, 200, 300, 400, 500];
        const msg = 'Hello World';
        const set = new Set(msg);
        
        console.log('数组:', arr);
        console.log('字符串:', msg);
        console.log('集合:', set);
        
        // 【代码注释】iter = arr[Symbol.iterator]() 得到独立遍历器;iter.next() 第一次即 100
        const iter = arr[Symbol.iterator]();
        console.log('遍历器对象:', iter);
        console.log('第一次next:', iter.next());
        
        // 【代码注释】for...of 取的是 **value**(非 key);Map 默认迭代 [k,v] 对
        console.log('---for...of遍历数组---');
        for (let v of arr) {
            console.log(v);
        }
        
        console.log('---for...of遍历字符串---');
        for (let v of msg) {
            console.log(v);
        }
        
        console.log('---for...of遍历Set---');
        for (let v of set) {
            console.log(v);
        }
        
        // 【代码注释】判断 typeof obj[Symbol.iterator] === 'function' 是检测 Iterable 的惯用法
        console.log('数组是否可遍历:', typeof arr[Symbol.iterator] === 'function');
        console.log('普通对象是否可遍历:', typeof {}[Symbol.iterator] === 'function');
        
        // 【代码注释】Iterable 触发点汇总:解构、展开、Array.from、new Set/Map、Promise.all/race、yield*
        console.log('---可遍历对象的应用场景---');
        
        // 【代码注释】1. 数组解构 --- 右侧须 Iterable,按迭代顺序绑定 first/second,rest 收集剩余
        const [first, second, ...rest] = arr;
        console.log('解构结果:', first, second, rest);
        
        // 【代码注释】2. 扩展运算符 --- 浅拷贝可迭代对象(同 Array.from 的迭代语义)
        const arrCopy = [...arr];
        console.log('数组复制:', arrCopy);
        
        // 【代码注释】3. Array.from(iterable) --- 将 Set/字符串/NodeList 等转为真数组
        const arrFrom = Array.from(set);
        console.log('Set转数组:', arrFrom);
        
        // 【代码注释】4. 构造器接收 Iterable --- new Set(arr) 去重;new Map(entries) 建映射
        const setFromArray = new Set(arr);
        console.log('数组创建Set:', setFromArray);
    </script>
</body>
</html>

【代码注释】可运行示例小结:for...of vs for...in:前者遍历 (Iterable),后者遍历可枚举键(含原型链)。数组遍历数值用 for...of,遍历索引可用 for...in 或 entries。

5.3 Iterator 与 Iterable 的关系

渲染错误: Mermaid 渲染失败: Parse error on line 5: ...方法] D -->|返回| E{value, done} C ----------------------^ Expecting 'TAGEND', 'STR', 'MD_STR', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'DIAMOND_START'

【代码注释】关系链:Iterable → [Symbol.iterator]() → Iterator → next(){value,done}。Iterator 的 [Symbol.iterator] 返回 this,故 for (const x of iterator) 合法但只能消费一次。

归纳:

  1. 所有 Iterator 都是 Iterable (Iterator 自身也部署了 Symbol.iterator,返回 this)。
  2. Iterable 不一定是 Iterator ;Iterable 通过 Symbol.iterator() 得到 Iterator。
  3. 遍历器对象是一次性的next() 耗尽后需重新调用 [Symbol.iterator]() 获取新遍历器。

5.5 for...of 与 Iterable 触发场景

所有部署了遍历器接口的数据 (含 Iterator 本身)都可使用 for...of

会隐式调用 Symbol.iterator 的典型场景
场景 说明
for...of 自动获取遍历器并循环 next()
数组 / 可迭代解构 const [a, b] = iterable
扩展运算符 [...set]fn(...arr)
Array.from() 将 Iterable 转为数组
new Set(iterable) 构造参数须可遍历
new Map(entries) 参数为键值对 Iterable
Promise.all(iterable) 并发处理可遍历的 Promise 列表
Promise.race(iterable) 竞态,取最先 settle 的 Promise
yield* 在生成器内委托另一个 Iterable
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>for...of 与 Iterable</title>
</head>
<body>
    <script>
        // 【代码注释】数组为 Iterable:for...of 每次得到元素值(非下标)
        const arr = [10, 20, 30];
        // 【代码注释】Map 默认迭代 entries,解构为 [键, 值] 二元组
        const map = new Map([['a', 1], ['b', 2]]);

        for (const v of arr) console.log(v);           // 10, 20, 30
        for (const [k, val] of map) console.log(k, val);  // a 1, b 2

        // 【代码注释】先 [...arr] 浅拷贝再 map,避免 mutate 原数组;展开依赖 Iterable 协议
        const doubled = [...arr].map(x => x * 2);
        console.log(doubled);  // [20, 40, 60]
    </script>
</body>
</html>

【代码注释】示例:for...of 取 arr/map 的for...in 遍历对象键且含继承可枚举属性。数组上 for...in 可能遍历到额外可枚举属性,生产环境数组优先 for...offorEach

5.6 伪数组与可遍历对象的区别

概念 定义 典型例子
伪数组(Array-like) length 与按索引访问,但不是 Array 实例 argumentsNodeList
Iterable 实现了 Symbol.iterator ArraySetMap、字符串
关系 二者概念独立;部分类型同时具备两种特征 字符串、NodeList 既是伪数组又是 Iterable;Set/Map 是 Iterable 但不是伪数组

【代码注释】伪数组不一定for...of(旧式 arguments 在部分环境需 Array.from);Iterable 不一定length(如 Set)。Array.from(arrayLike) 可同时利用 Iterable 或 length+索引将类数组转为真数组。

5.7 经典应用场景

  1. 自定义数据结构:为自定义对象添加遍历功能
  2. 数据流处理:处理流式数据
  3. 异步迭代:处理异步数据序列

5.8 市面应用实例

  • 异步编程:Async Iterator 用于异步数据流
  • 数据可视化:D3.js中使用迭代器处理数据
  • 测试框架:Jest等测试框架使用迭代器处理测试用例

6. 生成器Generator

生成器是ES6引入的一种特殊的函数,可以控制函数的执行流程。

6.1 生成器函数的基本语法

名词解析:

  • Generator Function(生成器函数)function* 声明,调用后不立即执行函数体,而是返回 Iterator。
  • yield :暂停点;next() 恢复执行直到下一个 yieldreturn
  • Iterator Resultnext() 的返回值形态 { value, done }
javascript 复制代码
// 【代码注释】function* 声明生成器;调用 generatorName() 返回 Generator(Iterator+Iterable)
function* generatorName() {
    yield 1;           // 暂停并产出 { value: 1, done: false }
    return 'done';     // 结束;return 值成为最后一次 next().value
}

【代码注释】function* 调用后返回 Generator 对象 (同时是 Iterator+Iterable),函数体不立即执行yield expr 暂停并产出 {value:expr,done:false}return v 结束且最后一次 next().value === vyield 仅能在 generator 函数或委托 yield* 中使用。

6.2 生成器的工作原理

Iterator Generator Client Iterator Generator Client #mermaid-svg-mmxOBhvpQhA7NDhv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mmxOBhvpQhA7NDhv .error-icon{fill:#552222;}#mermaid-svg-mmxOBhvpQhA7NDhv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mmxOBhvpQhA7NDhv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mmxOBhvpQhA7NDhv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mmxOBhvpQhA7NDhv .marker.cross{stroke:#333333;}#mermaid-svg-mmxOBhvpQhA7NDhv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mmxOBhvpQhA7NDhv p{margin:0;}#mermaid-svg-mmxOBhvpQhA7NDhv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mmxOBhvpQhA7NDhv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-mmxOBhvpQhA7NDhv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mmxOBhvpQhA7NDhv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-mmxOBhvpQhA7NDhv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-mmxOBhvpQhA7NDhv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-mmxOBhvpQhA7NDhv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-mmxOBhvpQhA7NDhv .sequenceNumber{fill:white;}#mermaid-svg-mmxOBhvpQhA7NDhv #sequencenumber{fill:#333;}#mermaid-svg-mmxOBhvpQhA7NDhv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-mmxOBhvpQhA7NDhv .messageText{fill:#333;stroke:none;}#mermaid-svg-mmxOBhvpQhA7NDhv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mmxOBhvpQhA7NDhv .labelText,#mermaid-svg-mmxOBhvpQhA7NDhv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-mmxOBhvpQhA7NDhv .loopText,#mermaid-svg-mmxOBhvpQhA7NDhv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-mmxOBhvpQhA7NDhv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-mmxOBhvpQhA7NDhv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-mmxOBhvpQhA7NDhv .noteText,#mermaid-svg-mmxOBhvpQhA7NDhv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-mmxOBhvpQhA7NDhv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mmxOBhvpQhA7NDhv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mmxOBhvpQhA7NDhv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-mmxOBhvpQhA7NDhv .actorPopupMenu{position:absolute;}#mermaid-svg-mmxOBhvpQhA7NDhv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-mmxOBhvpQhA7NDhv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-mmxOBhvpQhA7NDhv .actor-man circle,#mermaid-svg-mmxOBhvpQhA7NDhv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-mmxOBhvpQhA7NDhv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用生成器函数 返回Iterator 调用next() 执行到yield 返回yield值 返回{value, done} 调用next() 继续执行 返回下一个值

【代码注释】执行模型:调用 gen() 不跑函数体;首次 next() 跑到第一个 yield 暂停;再次 next() 从暂停点继续。适合惰性序列、状态机、旧式 co 异步流程(现多被 async/await 取代)。

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>生成器函数示例</title>
</head>
<body>
    <script>
        // 【代码注释】function* gen():星号紧挨 function;内部 yield 标记暂停点,return 提供 IteratorResult 最终值
        function* gen() {
            console.log('开始执行生成器');
            yield 100;
            console.log('继续执行');
            yield 200;
            console.log('再继续执行');
            yield 300;
            console.log('生成器即将结束');
            return '结束值';
        }
        
        // 【代码注释】typeof gen 仍为 'function';gen() 返回 Generator 对象,非普通返回值
        console.log('生成器类型:', typeof gen);  // function
        const iter = gen();
        console.log('遍历器对象:', iter);
        
        // 【代码注释】手动调用next方法
        console.log('---手动调用next---');
        console.log(iter.next());  // {value: 100, done: false}
        console.log(iter.next());  // {value: 200, done: false}
        console.log(iter.next());  // {value: 300, done: false}
        console.log(iter.next());  // {value: '结束值', done: true}
        
        // 【代码注释】for...of 忽略 return 值(只消费 yield);须 new iter2 = gen() 因 iter 已耗尽
        console.log('---for...of遍历---');
        const iter2 = gen();
        for (let value of iter2) {
            console.log('for...of值:', value);
        }
        
        // 【代码注释】lazyGen() 后不打印;首次 next 才输出「第一次yield」--- 证明按需计算、节省资源
        console.log('---惰性求值---');
        function* lazyGen() {
            console.log('第一次yield');
            yield 1;
            console.log('第二次yield');
            yield 2;
            console.log('第三次yield');
            yield 3;
        }
        
        const lazyIter = lazyGen();
        console.log('创建遍历器,还没执行');
        lazyIter.next();  // 此时才开始执行
        console.log('暂停');
        lazyIter.next();  // 继续执行
    </script>
</body>
</html>

【代码注释】可运行示例小结:next(arg) 可把 arg 作为上一个 yield 表达式的结果 传入。yield* 委托另一 Iterable。Redux-Saga、Koa 中间件历史基于生成器。

6.3 使用生成器为对象部署Iterator接口

完整示例
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>为对象部署Iterator接口</title>
</head>
<body>
    <script>
        // 【代码注释】user 为普通对象,默认无 [Symbol.iterator],不可 for...of
        const user = {
            name: '高小乐',
            age: 18,
            address: '浦东新区',
            users: ['刘姥姥', '马姥姥', '欧阳姥姥', '司马姥姥'],
            say: () => {}
        };
        
        // 【代码注释】赋值 generator 函数:for...in 遍历可枚举自有+继承属性(此处含 users 数组、say 等)
        user[Symbol.iterator] = function* () {
            for (let prop in this) {
                // 【代码注释】yield [prop, this[prop]] 产出键值对数组,与 Map entries 形态一致
                yield [prop, this[prop]];
            }
        };
        
        // 【代码注释】部署后 user 满足 Iterable,for...of 每次得到一个 [key,value] 对
        console.log('---for...of遍历对象---');
        for (let v of user) {
            console.log(v);  // ['name', '高小乐'], ['age', 18]...
        }
        
        // 【代码注释】const [a1,a2,a3] = user 从迭代器取前三项 --- 依赖自定义遍历顺序
        console.log('---数组解构---');
        const [a1, a2, a3] = user;
        console.log(a1);  // ['name', '高小乐']
        console.log(a2);  // ['age', 18]
        console.log(a3);  // ['address', '浦东新区']
        
        // 【代码注释】[...user] 收集全部迭代结果为数组,等价于 Array.from(user)
        console.log('---扩展运算符---');
        const entries = [...user];
        console.log(entries);  // 对象所有属性的键值对数组
        
        // 【代码注释】filter 动态重写 [Symbol.iterator] 为过滤后的生成器 --- 链式 API 模式
        console.log('---自定义遍历---');
        const customObject = {
            data: [1, 2, 3, 4, 5],
            filter(condition) {
                // 【代码注释】每次 filter 调用覆盖 iterator,返回 this 支持链式 [...customObject.filter(...)]
                this[Symbol.iterator] = function* () {
                    for (let item of this.data) {
                        if (condition(item)) {
                            yield item;  // 仅产出满足谓词的元素
                        }
                    }
                };
                return this;
            }
        };
        
        const filtered = customObject.filter(x => x % 2 === 0);
        console.log([...filtered]);  // [2, 4]
    </script>
</body>
</html>

【代码注释】可运行示例小结:自定义 Iterable 是统一遍历接口的关键。注意 for...in 与生成器遍历范围差异;生产环境更常用 Object.entries(obj)。过滤迭代可用生成器 function* filter(arr, pred){...}

6.4 生成器的高级应用

1. 无限序列
javascript 复制代码
// 【代码注释】惰性无限序列:仅在调用 next() 时计算下一项,不预分配大数组
function* naturalNumbers() {
    let n = 1;
    while (true) {
        yield n++;  // 每次 next 恢复执行到此处,产出当前 n 后自增
    }
}

const numbers = naturalNumbers();
console.log(numbers.next().value);  // 1 --- 首次 next 才启动函数体
console.log(numbers.next().value);  // 2
// 【代码注释】业务层应限制调用次数,避免 while(true) 导致逻辑上的「真无限」循环

【代码注释】无限序列:while(true) yield n++ 按需取 .next().value,内存 O(1) 状态。取太多仍可能逻辑死循环,须业务层限制次数。斐波那契、ID 生成器常用此模式。

2. 异步流程控制
javascript 复制代码
// 【代码注释】yield 右侧常为 Promise;需 co/redux-saga 等「执行器」反复 next 并注入结果
function* fetchUser() {
    const user = yield fetch('/api/user');       // 第一次 next 后暂停,执行器注入 user
    const posts = yield fetch(`/api/posts?userId=${user.id}`);
    return posts;  // 最后一次 next:{ value: posts, done: true }
}
// 【代码注释】现代项目优先 async/await;此模式用于理解 Saga 或历史中间件实现

【代码注释】异步示意:const user = yield fetch(...) 中 yield 右侧 Promise 由执行器(co)接管,next(user) 注入结果。现代代码优先 async/await,生成器保留在 Saga、教学场景。

3. yield* 委托:组合多个生成器
javascript 复制代码
// 【代码注释】yield* 将控制权委托给另一个 Iterable(含 Generator),直到其耗尽再返回
function* inner() {
    yield 'A';
    yield 'B';
    return '内部完成';  // return 值成为 yield* 表达式的结果,不被 for...of 消费
}

function* outer() {
    yield 1;
    // 【代码注释】yield* inner() 依次产出 'A'、'B';内部 return 值赋给 result(非 for...of 可见)
    const result = yield* inner();
    console.log('inner 返回值:', result);  // '内部完成'
    yield 2;
}

console.log([...outer()]);  // [1, 'A', 'B', 2] --- return 值不出现在序列中

// 【代码注释】yield* 可以委托任意 Iterable,不限于生成器 ------ 展平嵌套结构的利器
function* flatten(arr) {
    for (const item of arr) {
        if (Array.isArray(item)) {
            yield* flatten(item);  // 【代码注释】递归委托:处理任意深度嵌套数组,惰性展平
        } else {
            yield item;
        }
    }
}

const nested = [1, [2, [3, [4]], 5], 6];
console.log([...flatten(nested)]);  // [1, 2, 3, 4, 5, 6]

// 【代码注释】对比 Array.flat(Infinity):flatten generator 是惰性的,不创建中间数组,内存更友好
// Array.flat(Infinity) 需要全量构建结果数组;generator 版本 for...of 一项处理一项

【代码注释】yield* 三个要点:①委托任意 Iterable(含字符串、Set、另一个 Generator);②被委托 Generator 的 return 值作为 yield* 表达式结果,for...of 看不到;③递归 yield* 可惰性展平树状结构,是「树遍历 + 生产者模式」的最优写法。

4. Generator 实现状态机
javascript 复制代码
// 【代码注释】Generator 天然是状态机:每个 yield 对应一个状态,next() 触发状态迁移
function* trafficLight() {
    while (true) {
        console.log('🔴 红灯 --- 停车');
        yield 'red';
        console.log('🟡 黄灯 --- 准备');
        yield 'yellow';
        console.log('🟢 绿灯 --- 通行');
        yield 'green';
    }
}

const light = trafficLight();
light.next();  // 红灯
light.next();  // 黄灯
light.next();  // 绿灯
light.next();  // 再次红灯 --- 无限循环,按需驱动

// 【代码注释】对比 switch-case 状态机:Generator 版本无需外部 currentState 变量,状态内聚于函数体
// 每次 next 即「事件触发 + 状态迁移」,Redux-Saga 的 take/put 即此思想的异步版本

【代码注释】Generator 状态机优势:状态与逻辑内聚 在函数体内,无需外部 state 变量;while(true) 配合 yield 表达无限状态循环;暂停/恢复语义天然契合事件驱动。Redux-Saga 的 take 即「等待 action」的生成器暂停语义。

6.5 经典应用场景

  1. 状态机:使用生成器实现状态机
  2. 异步流程控制:简化异步代码
  3. 无限序列:生成无限的数据序列
  4. 数据流处理:处理大型数据集

6.6 市面应用实例

  • Redux-Saga:使用生成器管理副作用
  • Koa.js:使用生成器实现中间件
  • Co:使用生成器简化异步操作

7. 附录:扩展运算符与浅拷贝

扩展运算符 ... 可将可遍历对象 展开为逗号分隔序列,常用于对象合并与浅拷贝 。下列示例演示:拷贝后修改顶层属性互不影响,但嵌套引用仍共享

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>扩展运算符与浅拷贝</title>
</head>
<body>
    <script>
        const user01 = {
            name: '高小乐',
            address: '浦东新区',
            children: ['司马姥姥', '欧阳姥姥', '东方姥姥'],
            parent: {
                name: '高育良',
                age: 100,
                address: '朝阳区'
            }
        };

        // 【代码注释】引用赋值(非拷贝):user02 与 user01 同引用,改一处全变 --- 表单草稿勿用
        // 【代码注释】引用赋值:user02 与 user01 共享同一对象,修改会双向影响
        // const user02 = user01;

        // 【代码注释】扩展运算符浅拷贝:复制第一层自有可枚举属性;children/parent 仍为同一引用
        const user02 = { ...user01 };
        user02.name = '低大悲';              // 仅改 user02 顶层 name
        user02.children[2] = '西门姥姥';     // 共享 children 数组,user01 同步变化

        console.log(user01.name);           // '高小乐' --- 顶层字符串已隔离
        console.log(user01.children[2]);    // '西门姥姥' --- 嵌套引用未隔离,浅拷贝陷阱

        // 【代码注释】typeof [] 为 'object'(数组是对象);typeof Array 为 'function'(构造函数)
        console.log(typeof []);             // 'object'
        console.log(typeof Array);          // 'function'
    </script>
</body>
</html>

【代码注释】{ ...user01 } 仅复制第一层 自有可枚举属性;childrenparent 仍为引用。修改 user02.children[2] 会反映到 user01。深拷贝:structuredClone(注意函数、Symbol 限制)、lodash.cloneDeep、Immer produce

拷贝方式 顶层 嵌套引用 典型场景
const b = a 共享 共享 别名,非拷贝
{ ...a } / [...a] 复制 共享 表单草稿、Redux reducer 浅合并
structuredClone(a) 复制 深拷贝(部分类型除外) 复杂配置快照

8. 总结与最佳实践

8.1 知识点总结

运算符总结表
运算符 用途 典型场景 注意事项
** 幂运算 数学计算 注意运算优先级
?. 可选链 安全属性访问 不能用于赋值
?? 空值合并 默认值设置 只判断null/undefined
&&= 逻辑与赋值 条件赋值 左侧必须为变量
` =` 逻辑或赋值
??= 空值赋值 安全默认值 最安全的默认值方式
数据结构选择指南

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









数据结构选择
需要存储键值对?
键可以是任意类型?
值需要唯一?
Map/WeakMap
Object
Set/WeakSet
Array
键需要弱引用?
WeakMap
Map
值需要弱引用?
WeakSet
Set

【代码注释】决策树实践:API 列表去重 → Set;对象当键的缓存 → Map;组件实例私有元数据 → WeakMap;DOM 已处理标记 → WeakSet;有序下标集合 → Array;配置 JSON → Object。Weak 系列不可遍历、无 size。

8.2 最佳实践

1. 运算符使用建议(含代码)
javascript 复制代码
// ❌ 不推荐:|| 把合法的 0 / '' / false 都当默认值
function init(options) {
    const timeout = options.timeout || 3000;  // timeout=0 会被错误地变成 3000
    const label   = options.label   || 'default';  // label='' 会被错误替换
}

// ✅ 推荐:?? 只对 null/undefined 使用默认值
function init(options) {
    const timeout = options.timeout ?? 3000;   // 0 保留
    const label   = options.label   ?? 'default';  // '' 保留
    // 【代码注释】深层安全访问 + 默认值组合:常见 API 响应处理模式
    const userId  = options.user?.profile?.id ?? 'anonymous';
}

// 【代码注释】逻辑赋值:避免重复的 if 判断
const cache = {};
// ❌ 冗余写法:
if (cache.data === undefined || cache.data === null) cache.data = [];
// ✅ 简洁写法:
cache.data ??= [];
2. 数据结构使用建议(含代码)
javascript 复制代码
// 【代码注释】去重:Set 方案 O(n),filter+indexOf 方案 O(n²),数据量大时差异显著
const tags = ['js', 'css', 'js', 'html', 'css'];
const uniqueTags = [...new Set(tags)];  // ['js', 'css', 'html']

// 【代码注释】Map vs Object:键为非字符串类型时必须用 Map
const nodeMetadata = new Map();
const domNode = document.querySelector('#app');
nodeMetadata.set(domNode, { lastUpdated: Date.now() });
// Object 做不到:obj[domNode] 会把 DOM 对象 toString 为 '[object HTMLDivElement]',所有 DOM 节点变成同一键

// 【代码注释】WeakMap:临时挂载私有元数据,不阻止 GC,适合组件实例管理
const componentState = new WeakMap();
class Component {
    constructor() {
        componentState.set(this, { mounted: false, count: 0 });
    }
    mount() {
        componentState.get(this).mounted = true;
    }
}
3. 面向对象编程建议(含代码)
javascript 复制代码
// 【代码注释】组合优于继承:超过 2 层继承考虑改用组合或 Mixin
// ❌ 深层继承(难以维护,脆弱基类问题):
class Animal extends LivingThing extends Entity { ... }

// ✅ 组合(每个 capability 独立测试):
class Animal {
    #locomotion;
    #metabolism;
    constructor(type) {
        this.#locomotion = LocomotionFactory.create(type);
        this.#metabolism = new StandardMetabolism();
    }
    move() { return this.#locomotion.move(); }
}

// 【代码注释】#私有字段 vs _命名约定:# 在语法层强制,无法被子类或外部绕过
class BankAccount {
    #balance = 0;  // 真·私有:外部访问直接 SyntaxError

    deposit(amount) {
        if (amount <= 0) throw new RangeError('存款金额必须为正');
        this.#balance += amount;
    }

    get balance() { return this.#balance; }
}
4. 迭代器使用建议(含代码)
javascript 复制代码
// 【代码注释】分页数据惰性加载:生成器按需拉取,避免一次性加载全部页
async function* paginate(url) {
    let page = 1;
    while (true) {
        const res = await fetch(`${url}?page=${page}`);
        const data = await res.json();
        if (data.items.length === 0) return;
        yield* data.items;  // 【代码注释】yield* 委托数组,逐项产出当前页所有条目
        page++;
    }
}

// 【代码注释】for await...of 消费异步可遍历对象,处理无限流式数据
for await (const item of paginate('/api/products')) {
    console.log(item.name);
    if (someCondition) break;  // 【代码注释】break 触发生成器 return,清理资源
}

8.3 性能优化建议

javascript 复制代码
// 【代码注释】惰性管道:链式 map/filter/take 不创建中间数组,内存友好
function* map(iter, fn) {
    for (const x of iter) yield fn(x);
}

function* filter(iter, pred) {
    for (const x of iter) if (pred(x)) yield x;
}

function* take(iter, n) {
    let count = 0;
    for (const x of iter) {
        if (count++ >= n) return;
        yield x;
    }
}

// 【代码注释】处理大数据集时,以下写法比 array.map().filter().slice() 节省大量中间内存
function* range(n) { for (let i = 0; i < n; i++) yield i; }

const result = [...take(filter(map(range(1_000_000), x => x * 2), x => x % 6 === 0), 5)];
console.log(result);  // [0, 6, 12, 18, 24] --- 实际只计算了 12 次,非 100 万次
  1. 避免不必要的中间数组:使用生成器管道处理大数据集,内存占用 O(1)
  2. 合理使用 WeakMap/WeakSet:存储 DOM/实例关联数据,防止内存泄漏
  3. Symbol 常量替代字符串枚举 :防止因拼写错误导致的 === 比较失败

8.4 兼容性与工程化建议

特性 Chrome Firefox Safari Node.js Polyfill 方案
?. ?? 80+ 74+ 13.1+ 14+ @babel/plugin-proposal-optional-chaining
??= &&= ` =` 85+ 79+ 14+
# 私有字段 74+ 90+ 14.1+ 12+ @babel/plugin-proposal-class-properties
Generator 39+ 36+ 10+ 4+ 无需 polyfill(现代项目)
WeakRef 84+ 79+ 14.5+ 14+ 无原生 polyfill,按需降级
  • TypeScript 类型检查 :启用 "strictNullChecks": true?./?? 与类型系统协同,编译期发现 null 访问
  • ESLint 规则推荐prefer-nullish-coalescing(强制 ?? 而非 ||)、no-unsafe-optional-chaining(禁止 ?. 结果直接作算术操作数)

8.5 市面发展趋势

  1. 异步迭代器普及for await...of + 异步 Generator 已成为流式数据标准模式(Node.js Streams、Web Streams API)
  2. Records & Tuples 提案(Stage 2)#{ x: 1 } 不可变值对象,与 === 深比较,Map/Set 键语义更直观
  3. Pattern Matching 提案(Stage 2)match(value) { when { type: 'A' }: ... } 类似 Rust 的模式匹配,彻底替代 switch-case
  4. Iterator Helpers(Stage 3) :内置 .map().filter().take() 等迭代器方法,无需手写惰性管道工具函数

结语

ES6 及后续规范为 JavaScript 补齐了安全运算符唯一键 SymbolClass 语法糖集合类型统一迭代协议 。它们不仅是语法层面的便利,更是 React Hooks 依赖数组去重、Vue 响应式 Map 存储、Koa 中间件生成器、Redux-Saga 流程编排等工程实践的底层语言基础。

建议对照本文各节的可运行 HTML 示例 在浏览器控制台验证输出,并查阅 MDN 迭代协议SymbolSetMap 等官方文档深化理解。

【选型速查】

场景 推荐特性 原因
设置默认值(保留 0 / '' / false) ?? 精确 nullish 判断
安全访问深层属性 ?. 组合 ?? 短路 + 默认值
数组去重 [...new Set(arr)] O(n),保序
键为对象/DOM 的缓存 Map / WeakMap 任意类型键,WeakMap 防泄漏
已处理过的节点标记 WeakSet 弱引用,节点移除自动释放
库扩展第三方对象属性 Symbol 防命名冲突,不被 for-in 枚举
对象行为自定义(转换/迭代) Symbol.toPrimitive / Symbol.iterator 元编程钩子,不破坏原有接口
多能力组合(非单一继承链) Mixin 模式 能力正交,按需组合
惰性序列 / 大数据流处理 Generator + yield* 按需计算,O(1) 内存状态
LRU / 有序 K-V 缓存 Map(利用插入顺序) size O(1),键任意类型

在团队项目中配合 ESLintprefer-nullish-coalescingno-unsafe-optional-chaining)与 TypeScript strictNullChecks,可在编译期发现绝大多数空值访问与类型误用风险。

相关推荐
西部荒野子1 小时前
Zustand 状态管理规范:别让轻量状态变成隐形通知风暴
前端·javascript
Carson带你学Android1 小时前
Kotlin放大招!官方 Skills 直接喂出「专家级」代码
android·前端·kotlin
之歆1 小时前
Day04_ES6完全指南:从入门到精通的现代化JavaScript开发
前端·javascript·es6
Coffeeee1 小时前
一个kotlin的Smart cast导致的编译问题
android·前端·kotlin
CodeSheep1 小时前
胡彦斌都开始苦修Vibe Coding,还上架App Store,都卷到编程来了吗?
前端·后端·程序员
薄荷椰果抹茶1 小时前
前端技术之---打字机效果与流式输出
前端
Mintopia1 小时前
Tanstack为什么会火
前端
DongWook1 小时前
关于Harness Engineering的一次实践
前端·后端
风骏时光牛马1 小时前
Kotlin开发高频疑难问题汇总梳理
前端