ES6 深度解析与实战应用:运算符、Symbol、Class、集合与迭代协议
本文系统梳理 ES2015 及后续规范中的新增运算符 、Symbol 、Class 语法 、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 | 原始类型,不可 new,Symbol.for 全局注册 |
防属性名冲突、元编程钩子 |
| Class | 本质是构造函数语法糖,typeof 为 function |
结构化 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;?? 仅识别 Nullish (null/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)。工程注意:超大整数幂用 BigInt(2n ** 10n);混合类型会触发 ToNumber 转换。
经典应用场景
- 科学计算:在涉及物理、数学计算时,指数运算符提供了简洁的表示方式
- 算法实现:在动态规划、分治算法中经常涉及幂运算
- 数据处理:在处理指数增长或衰减的数据模型时
市面应用实例
- 金融科技:计算复利时,如计算投资回报率
- 游戏开发:经验值计算、伤害公式设计
- 数据可视化:在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」。
技术优势
- 代码简洁性 :减少了大量的
if判断和&&短路逻辑 - 错误防范:避免因属性不存在而导致的运行时错误
- 可读性提升:代码意图更加明确,表达"安全访问"的语义
经典应用场景
- API响应处理:处理可能缺失字段的API数据
- 表单验证:安全地访问嵌套的表单数据
- 配置管理:访问可能不存在的配置项
市面应用实例
- React应用:访问props中的嵌套数据
- Vue应用:处理可能为空的响应式数据
- Angular应用:安全访问服务返回的数据结构
1.3 空值判断运算符
语法原理
空值判断运算符 ?? 是一个逻辑运算符,当左侧的操作数为 null 或 undefined 时,返回右侧的操作数,否则返回左侧的操作数。
与逻辑或运算符的区别
javascript
// 【代码注释】|| 左侧为 falsy(含 0、''、false、null、undefined、NaN)时取右侧
0 || 100; // 100 --- 0 被当作「无有效值」
// 【代码注释】?? 仅 null/undefined 取右侧;0、''、false 视为业务有效值而保留
0 ?? 100; // 0 --- 分页 page=0、价格 0 元等场景必须用 ??
【代码注释】?? 为 Nullish Coalescing :仅当左侧为 null 或 undefined 取右侧。与 || 对比: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 可辅助团队统一。注意:?? 与 &&/|| 混用时须加括号(?? 优先级低于 ||)。
名词解释
- 空值 :特指
null和undefined两个值 - Falsy值 :在布尔上下文中被视为false的值,包括
0、false、''、null、undefined、NaN - Nullish Coalescing:空值合并的英文名称
经典应用场景
- 配置管理:为配置项提供默认值
- 数据处理:处理可能缺失的数据字段
- 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 ||= y、x ??= 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 缺省;&&= 用于「有值才覆盖」的守卫赋值。
技术特点
- 惰性求值:只有在需要时才会执行右侧表达式
- 链式操作:可以链式使用多个逻辑赋值运算符
- 内存优化:避免了不必要的中间变量
经典应用场景
- 状态管理:初始化应用状态
- 配置合并:为缺失的配置项提供默认值
- 条件更新:根据当前值决定是否更新
市面应用实例
- React Hooks:在自定义Hook中设置初始状态
- Vue Composition API:管理响应式状态的默认值
- Node.js配置:加载和管理应用配置
2. Symbol 类型深度解析
Symbol 是 ES2015 新增的第七种原始类型 (与 string、number 等并列)。根据 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...in、Object.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...in、Object.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)、map、filter 等惰性管道,在大数据场景避免中间数组。
2.6 经典应用场景
- 自定义迭代器:为对象定义默认的迭代行为
- 元编程:改变JavaScript引擎的默认行为
- 类库开发:为第三方对象添加自定义方法而不冲突
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 {} 在运行时仍创建构造函数 C,typeof C === 'function'。类体中的简写方法 挂到 C.prototype;类字段 (name = 1)挂到实例自身(ES2022 公有字段)。类声明存在 TDZ,不能在声明前引用。
Class 语法特点归纳
class定义的「类」本质是构造函数,typeof为'function'。- 类只能
new实例化,不能像普通函数一样直接调用。 - 类体内简写方法 挂在
prototype上;类字段 (name = 'xx')挂在实例自身。 - 类体内只能写属性、方法声明;其他逻辑应写在方法或构造器内。
完整示例
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 私有模式相比,# 语法更简洁且由引擎强制。
私有属性的优势
- 封装性:真正的私有性,外部无法访问
- 安全性:防止外部直接修改内部状态
- 重构友好:内部实现改变不影响外部代码
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.defineProperty 的 get/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 或组合。
继承规则归纳
- 子类中同名 属性或方法会覆盖父类成员。
- 子类重写
constructor时,必须先super(...),且super须写在构造器最前面。 - 子类实例的原型链:
实例 → 子类.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 经典应用场景
- 组件开发:创建可复用的 UI 组件类
- 数据模型:定义数据模型和业务逻辑
- 工具类:创建功能性的工具类库
- 扩展内置类型 :如
class MyArray extends Array在保留数组 API 的同时增加领域方法 - 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 判等(NaN 与 NaN 视为同一成员)。无键 ,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类似,但有几个重要区别:
- 只能存储对象类型
- 对象是弱引用,不计入垃圾回收机制
- 不可遍历
完整示例
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的应用场景
- DOM节点存储:存储DOM节点而不影响垃圾回收
- 对象标记:标记对象而不阻止其被垃圾回收
- 私有数据存储:为对象关联私有数据
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应用场景
- 数组去重:最经典的应用场景
- 集合运算:交集、并集、差集等
- 标签系统:确保标签不重复
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应用场景
- 缓存系统:键值对缓存
- 配置管理:复杂配置的存储
- 数据映射:复杂数据结构的关联
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: true 时 value 常为 undefined(return 的值会成为最后一次 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) 合法但只能消费一次。
归纳:
- 所有 Iterator 都是 Iterable (Iterator 自身也部署了
Symbol.iterator,返回this)。 - Iterable 不一定是 Iterator ;Iterable 通过
Symbol.iterator()得到 Iterator。 - 遍历器对象是一次性的 :
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...of 或 forEach。
5.6 伪数组与可遍历对象的区别
| 概念 | 定义 | 典型例子 |
|---|---|---|
| 伪数组(Array-like) | 有 length 与按索引访问,但不是 Array 实例 |
arguments、NodeList |
| Iterable | 实现了 Symbol.iterator |
Array、Set、Map、字符串 |
| 关系 | 二者概念独立;部分类型同时具备两种特征 | 字符串、NodeList 既是伪数组又是 Iterable;Set/Map 是 Iterable 但不是伪数组 |
【代码注释】伪数组不一定 可 for...of(旧式 arguments 在部分环境需 Array.from);Iterable 不一定 有 length(如 Set)。Array.from(arrayLike) 可同时利用 Iterable 或 length+索引将类数组转为真数组。
5.7 经典应用场景
- 自定义数据结构:为自定义对象添加遍历功能
- 数据流处理:处理流式数据
- 异步迭代:处理异步数据序列
5.8 市面应用实例
- 异步编程:Async Iterator 用于异步数据流
- 数据可视化:D3.js中使用迭代器处理数据
- 测试框架:Jest等测试框架使用迭代器处理测试用例
6. 生成器Generator
生成器是ES6引入的一种特殊的函数,可以控制函数的执行流程。
6.1 生成器函数的基本语法
名词解析:
- Generator Function(生成器函数) :
function*声明,调用后不立即执行函数体,而是返回 Iterator。 - yield :暂停点;
next()恢复执行直到下一个yield或return。 - Iterator Result :
next()的返回值形态{ 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 === v。yield 仅能在 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 经典应用场景
- 状态机:使用生成器实现状态机
- 异步流程控制:简化异步代码
- 无限序列:生成无限的数据序列
- 数据流处理:处理大型数据集
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 } 仅复制第一层 自有可枚举属性;children、parent 仍为引用。修改 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 万次
- 避免不必要的中间数组:使用生成器管道处理大数据集,内存占用 O(1)
- 合理使用 WeakMap/WeakSet:存储 DOM/实例关联数据,防止内存泄漏
- 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 市面发展趋势
- 异步迭代器普及 :
for await...of+ 异步 Generator 已成为流式数据标准模式(Node.js Streams、Web Streams API) - Records & Tuples 提案(Stage 2) :
#{ x: 1 }不可变值对象,与===深比较,Map/Set 键语义更直观 - Pattern Matching 提案(Stage 2) :
match(value) { when { type: 'A' }: ... }类似 Rust 的模式匹配,彻底替代switch-case - Iterator Helpers(Stage 3) :内置
.map()、.filter()、.take()等迭代器方法,无需手写惰性管道工具函数
结语
ES6 及后续规范为 JavaScript 补齐了安全运算符 、唯一键 Symbol 、Class 语法糖 、集合类型 与统一迭代协议 。它们不仅是语法层面的便利,更是 React Hooks 依赖数组去重、Vue 响应式 Map 存储、Koa 中间件生成器、Redux-Saga 流程编排等工程实践的底层语言基础。
建议对照本文各节的可运行 HTML 示例 在浏览器控制台验证输出,并查阅 MDN 迭代协议、Symbol、Set、Map 等官方文档深化理解。
【选型速查】
| 场景 | 推荐特性 | 原因 |
|---|---|---|
| 设置默认值(保留 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),键任意类型 |
在团队项目中配合 ESLint(prefer-nullish-coalescing、no-unsafe-optional-chaining)与 TypeScript strictNullChecks,可在编译期发现绝大多数空值访问与类型误用风险。