Day04_ES6完全指南:从入门到精通的现代化JavaScript开发
ES6完全指南:从入门到精通的现代化JavaScript开发
面向 Day04 的 ES6 总结博客:数据类型、变量声明、数组扁平化/拷贝、对象属性描述符与深拷贝。理论参考 MDN JavaScript、ECMA-262。
目录
- 0. 知识脉络与名词百科
- 1. ECMAScript概述
- 2. 数据类型系统
- 3. 变量声明方式
- 4. 数组操作深度解析
- 5. 对象属性特性详解
- 6. 深拷贝与浅拷贝
- 7. 实战应用场景
- 8. 性能优化建议
- 9. 最佳实践总结
- 10. Day04 知识点速查
- 附录:官方与延伸阅读
0. 知识脉络与名词百科
0.1 本章知识图谱
Day04 在 ES6 语法底座上,聚焦 类型体系 → 变量声明 → 数组工具 → 对象描述符 → 拷贝策略 五条主线。
【代码注释】 完整知识主线:类型体系(值/引用)→ 块级变量(let/const/TDZ)→ 函数增强(箭头函数/词法 this)→ 解构/展开/Rest → ES6 模块(ESM/Tree Shaking)→ Class 类系统(私有字段/继承/Mixin)→ Iterator/Generator(惰性求值/状态机)→ Proxy/Reflect(响应式/元编程)→ 数组工具(flat/拷贝)→ 描述符/访问器 → 拷贝策略(浅/深/structuredClone)。这些特性在框架源码中深度交织:Vue 3 = Proxy + Reflect;Redux = 展开运算符 + 不可变更新;React Hooks = 闭包 + Generator 思想。
0.2 核心名词解析
| 名词 | 解析 |
|---|---|
| 原始类型 | number、string、boolean、null、undefined、bigint、symbol;值传递 |
| 引用类型 | Object、Array、Function 等;变量存地址,赋值共享引用 |
| 块级作用域 | {} 内 let/const 仅块内可见,无 var 提升问题 |
| TDZ | 暂时性死区(Temporal Dead Zone),let/const 声明前不可访问 |
| 属性描述符 | value/writable/enumerable/configurable 控制属性行为 |
| 访问器属性 | get/set,用于计算属性、数据校验(Vue 响应式基础) |
| 浅拷贝 | 只复制第一层;[...arr]、Object.assign |
| 深拷贝 | 递归复制嵌套结构;需注意循环引用与特殊类型 |
| Iterator | 有 next() 的对象,每次返回 {value, done},驱动 for...of |
| Generator | function* 函数,yield 暂停执行,实现惰性求值与协程 |
| Proxy | 拦截对象操作(读/写/删除等),可插入校验、日志、响应式逻辑 |
可选链 ?. |
访问链中遇 null/undefined 短路返回 undefined,不抛错 |
空值合并 ?? |
仅 null/undefined 时回退,保留 0、''、false 等合法假值 |
业界典型落地:
- React:不可变更新依赖浅拷贝 + 展开运算符;Fiber 调度借鉴 Generator 协程思想
- Vue 2:
Object.defineProperty基于属性描述符实现响应式 - Vue 3:
Proxy+Reflect实现细粒度依赖收集,解决 Vue 2 数组/新增属性检测盲区 - Redux Toolkit:
structuredClone/Immer 实现不可变状态更新 - lodash:
cloneDeep与手写深拷贝的取舍 - styled-components / graphql-tag:标签模板字符串(Tagged Template)实现 DSL
1. ECMAScript概述
1.1 什么是ECMAScript?
ECMAScript是JavaScript的标准化规范,由ECMA国际组织(欧洲计算机制造商协会)制定和维护。它是JavaScript的核心语法和功能基础,而JavaScript则是ECMAScript规范的具体实现。
名词解析:
- ECMA: European Computer Manufacturers Association(欧洲计算机制造商协会)
- Script: 脚本语言
- ES6: ECMAScript 2015的别名,是JavaScript历史上最重要的更新之一
- TC39: 负责ECMAScript标准化的技术委员会
1.2 ES6的重要意义
ES6(ECMAScript 2015)是JavaScript发展史上的重要里程碑,引入了大量现代化的语法特性和功能,极大地提升了开发效率和代码质量。
核心特性概览:
【代码注释】 ES6 核心特性思维导图:let/const 块级作用域、箭头函数词法 this、解构赋值、Class/模块——每项特性都是下一节内容的前置知识。
2. 数据类型系统
2.1 完整的数据类型分类
JavaScript数据类型体系架构:
【代码注释】 JavaScript 数据类型树:7 种原始类型(值传递)+ 多种引用类型(引用传递)。掌握此图可有效避免「修改对象属性影响到原始变量」类 Bug。
2.2 原始类型详解
名词解析:原始类型(Primitive Types)
原始类型是JavaScript中最基本的数据类型,它们是不可变的,直接存储在栈内存中。
| 类型 | 说明 | 示例 | 典型应用场景 |
|---|---|---|---|
number |
数值类型,包括整数和浮点数 | 42, 3.14, NaN |
数学计算、统计数据 |
string |
字符串类型 | "hello", 'world' |
文本处理、用户输入 |
boolean |
布尔类型 | true, false |
条件判断、状态标记 |
null |
空值,表示"无"的对象 | null |
重置变量、清空引用 |
undefined |
未定义值 | undefined |
变量未初始化 |
bigint |
大整数类型 | 9007199254740991n |
精确的大数计算 |
symbol |
唯一标识符 | Symbol('id') |
对象属性键、避免冲突 |
实战示例:
// 【代码注释】原始类型实战:BigInt 与 Symbol 的典型用法与易错点
// Number 在 ±2^53-1 之外会丢失精度(浮点二进制表示的局限)
const maxSafeNumber = Number.MAX_SAFE_INTEGER; // 9007199254740991,安全整数上界
const unsafe = 9007199254740992; // 与 9007199254740993 在 Number 下可能相等
const bigNumber = 9007199254740992n; // 字面量后缀 n 表示 BigInt,任意大整数精确运算
// Symbol:每次 Symbol() 都生成全局唯一值,description 仅用于调试显示,不参与相等性比较
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false —— 不能用 === 判断「同名」Symbol
// 计算属性名 [Symbol('id')]:该键不会出现在 Object.keys / for...in 中(除非 enumerable: true)
const user = {
[Symbol('id')]: 123,
name: "张三",
age: 25
};
【代码注释】 Number.MAX_SAFE_INTEGER 标识安全整数边界,超出后应改用 BigInt(n 后缀);Symbol 适合作为「私有」或库内部属性键,避免与业务字段名冲突,且默认不可枚举,常用于框架内部元数据(如 React 元素类型标记)。
2.3 对象类型详解
名词解析:对象类型(Object Types)
对象类型是复合数据类型,可以存储多个值和复杂的数据结构。对象存储在堆内存中,变量通过引用访问对象。
核心对象类型应用场景:
// 【代码注释】Array:有序、可重复,下标访问 O(1),适合列表、队列、表格行数据
const shoppingCart = ['苹果', '香蕉', '橙子'];
// 注意:数组是引用类型,赋值/传参只复制引用,修改元素会影响同一引用
// 【代码注释】Set:基于 SameValueZero 去重,插入/查找平均 O(1),值唯一
const uniqueTags = new Set(['JavaScript', 'React', 'JavaScript']);
console.log(uniqueTags.size); // 2 —— 重复 'JavaScript' 被自动忽略
// 与 Array.filter 去重相比:语义更清晰,且 has/add/delete 语义专为集合设计
// 【代码注释】Map:键可为任意类型(对象、函数),保持插入顺序,适合非字符串键的 KV 存储
const userSessions = new Map();
userSessions.set('session_123', { userId: 1, loginTime: Date.now() });
// 对比普通对象:Map 键数量用 .size 获取,无原型链污染,频繁增删性能更稳定
// 【代码注释】Date:解析 ISO 8601 字符串;时区与本地化显示需配合 toLocaleString 等 API
const appointment = new Date('2024-12-25T10:00:00');
// 注意:月份在部分 API 中为 0-11(getMonth),与日常「1 月=1」不一致
【代码注释】 选型口诀:要顺序与重复 → Array;要唯一集合 → Set;键非字符串或需稳定迭代顺序 → Map;时间轴与定时 → Date。四者均为引用类型,变量存的是堆上对象的地址。
实际应用示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript数据类型实际应用</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.demo-section {
margin: 20px 0;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.demo-section h3 {
color: #667eea;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.button {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
transition: background 0.3s;
}
.button:hover {
background: #764ba2;
}
.result {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-top: 10px;
font-family: 'Courier New', monospace;
}
.tag {
display: inline-block;
background: #e3f2fd;
color: #1976d2;
padding: 5px 10px;
border-radius: 15px;
margin: 5px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>JavaScript数据类型实战应用</h1>
<div class="demo-section">
<h3>🛒 购物车系统(Array应用)</h3>
<button class="button" onclick="addToCart()">添加商品</button>
<button class="button" onclick="removeFromCart()">移除商品</button>
<button class="button" onclick="showCart()">查看购物车</button>
<div id="cartResult" class="result"></div>
</div>
<div class="demo-section">
<h3>🏷️ 标签管理系统(Set应用)</h3>
<input type="text" id="tagInput" placeholder="输入标签">
<button class="button" onclick="addTag()">添加标签</button>
<div id="tagsResult" class="result"></div>
</div>
<div class="demo-section">
<h3>⏰ 会话管理系统(Map应用)</h3>
<button class="button" onclick="createSession()">创建会话</button>
<button class="button" onclick="checkSession()">检查会话</button>
<div id="sessionResult" class="result"></div>
</div>
</div>
<script>
// 【代码注释】模块一:购物车 —— 演示 Array 的 push/pop/map 与 DOM 联动
let shoppingCart = []; // let:购物车内容会随用户操作变化,需重新赋值/变异
const products = ['iPhone 15', 'MacBook Pro', 'AirPods', 'iPad', 'Apple Watch'];
function addToCart() {
// Math.floor(Math.random() * length) 生成 [0, length-1] 均匀随机下标
const randomProduct = products[Math.floor(Math.random() * products.length)];
shoppingCart.push(randomProduct); // 尾部入队,均摊 O(1)
showCart(); // 每次变更后同步刷新视图(简单单向数据流)
}
function removeFromCart() {
if (shoppingCart.length > 0) {
shoppingCart.pop(); // 移除最后一项;空数组 pop 返回 undefined 但不抛错
showCart();
}
}
function showCart() {
const resultDiv = document.getElementById('cartResult');
if (shoppingCart.length === 0) {
resultDiv.innerHTML = '购物车为空';
} else {
// map 生成带序号的 HTML 片段,join 拼接;注意此处未转义,生产环境需防 XSS
resultDiv.innerHTML = `
<strong>购物车内容:</strong><br>
${shoppingCart.map((item, index) => `${index + 1}. ${item}`).join('<br>')}
<br><strong>商品数量:</strong> ${shoppingCart.length}
`;
}
}
// 【代码注释】模块二:标签 —— Set 保证用户输入的标签名全局唯一
const tags = new Set(['JavaScript', '前端开发', 'Web技术']);
function addTag() {
const input = document.getElementById('tagInput');
const newTag = input.value.trim(); // trim 过滤纯空格,避免无效条目
if (newTag) {
tags.add(newTag); // 已存在则静默忽略,Set 不抛错也不重复存储
input.value = '';
showTags();
}
}
function showTags() {
const resultDiv = document.getElementById('tagsResult');
// Set 不可直接 map,先 Array.from 转为数组再渲染(保持插入顺序)
resultDiv.innerHTML = `
<strong>当前标签:</strong><br>
${Array.from(tags).map(tag => `<span class="tag">${tag}</span>`).join('')}
<br><strong>标签数量:</strong> ${tags.size}(自动去重)
`;
}
showTags(); // 页面加载时渲染初始标签集合
// 【代码注释】模块三:会话 —— Map 以 sessionId 为键,值为会话元数据对象
const sessions = new Map();
function createSession() {
const sessionId = 'session_' + Date.now(); // 时间戳保证键在单机演示中唯一
const sessionData = {
userId: Math.floor(Math.random() * 1000),
loginTime: new Date().toLocaleString(), // 本地化时间字符串,便于直接展示
ip: '192.168.1.' + Math.floor(Math.random() * 255)
};
sessions.set(sessionId, sessionData); // O(1) 写入;值对象与键独立存储
showSessions();
}
function checkSession() {
const resultDiv = document.getElementById('sessionResult');
if (sessions.size === 0) {
resultDiv.innerHTML = '没有活跃会话';
} else {
// entries() 返回迭代器;.next().value 取第一条 [key, value],演示 Map 遍历协议
const firstSession = sessions.entries().next().value;
if (firstSession) {
const [sessionId, sessionData] = firstSession; // 数组解构,与 Map 条目结构一致
resultDiv.innerHTML = `
<strong>会话详情:</strong><br>
会话ID: ${sessionId}<br>
用户ID: ${sessionData.userId}<br>
登录时间: ${sessionData.loginTime}<br>
IP地址: ${sessionData.ip}<br>
<strong>活跃会话总数:</strong> ${sessions.size}
`;
}
}
}
function showSessions() {
checkSession(); // 复用检查逻辑,创建会话后立即展示
}
</script>
</body>
</html>
【代码注释】 完整可运行 HTML 示例:购物车(Array)、标签去重(Set)、会话管理(Map)三大场景并列演示;Set.add 自动去重,Map.set/get 支持任意类型键。
3. 变量声明方式
3.1 变量声明演进史
JavaScript变量声明的完整演进:
【代码注释】 变量声明演进时间线:var(ES5,函数作用域,可提升)→ let/const(ES6,块级作用域,TDZ)→ class/import(ES6,静态分析友好)。
3.2 六种变量声明方式详解
完整的变量声明方式:
| 声明方式 | 引入版本 | 作用域 | 提升行为 | 重复声明 | 典型应用场景 |
|---|---|---|---|---|---|
var |
ES5 | 函数作用域 | 存在提升 | 允许 | 遗留代码维护 |
let |
ES6 | 块级作用域 | 暂时性死区 | 禁止 | 一般变量声明 |
const |
ES6 | 块级作用域 | 暂时性死区 | 禁止 | 常量声明 |
function |
ES5 | 函数作用域 | 完整提升 | 允许 | 函数声明 |
class |
ES6 | 块级作用域 | 暂时性死区 | 禁止 | 类定义 |
import |
ES6 | 模块作用域 | 静态提升 | 禁止 | 模块导入 |
3.3 let和const的核心特性
名词解析:暂时性死区(Temporal Dead Zone,TDZ)
暂时性死区是指从代码块开始到变量声明语句之间的区域,在这个区域内变量无法访问。
// 【代码注释】let / const 与 var 的行为差异 —— 建议在控制台逐段取消注释观察报错
console.log('=== let和const核心特性演示 ===');
// ① 同一作用域内不可重复声明(编译期 SyntaxError,优于运行时才发现的 var 覆盖)
let userName = "张三";
// let userName = "李四"; // SyntaxError: Identifier 'userName' has already been declared
const MAX_USERS = 100;
// const MAX_USERS = 200; // 同上;const 还要求声明时必须初始化
// ② 暂时性死区 TDZ:从块开始到 let/const 语句之前,访问标识符会抛 ReferenceError
// console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 25; // 与 var 不同:var 会提升并初始化为 undefined
// ③ 块级作用域:仅 {} / if / for 等块内有效,块外无法访问(避免 var 泄漏到外层)
{
let blockScoped = "只在块内可见";
const BLOCK_CONSTANT = "块级常量"; // const 绑定不可重新赋值,但对象属性仍可改
console.log('块内访问:', blockScoped);
}
// console.log(blockScoped); // ReferenceError: blockScoped is not defined
// ④ 脚本顶层 let/const 不成为 window 属性,减少全局命名空间污染(模块化前提)
let globalLet = "全局let";
const globalConst = "全局const";
var globalVar = "全局var"; // var 在浏览器全局脚本中会挂到 window
console.log('window.globalVar:', window.globalVar); // "全局var"
console.log('window.globalLet:', window.globalLet); // undefined
console.log('window.globalConst:', window.globalConst); // undefined
【代码注释】 四条规则对应四条防线:防重复声明、防「先用后声明」、防作用域泄漏、防污染 window。面试常问:TDZ 在 typeof 未声明变量时仍抛错;const 冻结的是绑定而非对象内部属性。
3.4 变量声明最佳实践
实战应用场景:
// 【代码注释】声明策略:默认 const,只有需要重新绑定标识符时才用 let,避免 var
// 模块级常量:引用地址不变,业务上视为「配置项」
const API_BASE_URL = 'https://api.example.com';
const DEFAULT_TIMEOUT = 5000;
const CONFIG = {
debug: true,
version: '1.0.0'
}; // 注意:CONFIG 本身不能 CONFIG = {},但 CONFIG.debug 仍可改,除非再 freeze
// 会随用户交互或请求结果变化的状态,用 let
let currentPage = 1;
let searchQuery = '';
let userSession = null; // 先 null 占位,登录后再赋对象引用
// 【代码注释】类 + 静态字段 + 浅冻结:适合应用启动时一次性写入的配置
class AppConfig {
// static 字段属于类本身,所有实例共享,常用于版本号、重试上限等
static API_VERSION = 'v2';
static MAX_RETRIES = 3;
static DEFAULT_LOCALE = 'zh-CN';
constructor(config = {}) {
// 外层 freeze:禁止替换 this.config 或增删顶层键
// 内层 endpoints 再 freeze:防止运行时被改掉路径常量
this.config = Object.freeze({
timeout: config.timeout || 30000,
debug: config.debug || false,
endpoints: Object.freeze({
users: '/users',
posts: '/posts',
comments: '/comments'
})
});
}
}
const appConfig = new AppConfig({ timeout: 5000 });
// appConfig.config.timeout = 10000;
// 非严格模式:静默失败;严格模式:TypeError(属性不可写)
// 深层嵌套对象若未 freeze,仍可能被修改 —— 深冻结需递归或 structuredClone + 只读代理
【代码注释】 ESLint prefer-const 背后的理由:减少可变状态,便于推理与 Tree Shaking。Object.freeze 是浅冻结,嵌套对象需逐层处理;与 readonly(TS)或 Immutable 数据结构配合才是生产级不可变配置。
3.5 箭头函数(Arrow Function)
3.5.1 语法与 this 绑定原理
为什么需要箭头函数?
传统函数的 this 由调用时动态决定,这在回调中极易引发 this 丢失问题。箭头函数没有自己的 this——它从定义时的词法作用域捕获外层 this,彻底解决了这类痛点。
// 【代码注释】对比:普通函数作为回调时 this 由「谁调用」决定,箭头函数由「定义处外层」决定
// ❌ setInterval 以普通函数调用回调,严格模式下 this 为 undefined,非严格为 window
function Timer() {
this.seconds = 0;
setInterval(function () {
this.seconds++; // 实际修改的是 window.seconds 或报错,与 Timer 实例无关
console.log(this.seconds); // NaN 或 undefined
}, 1000);
}
// ✅ 箭头函数不绑定自己的 this,闭包捕获构造执行时的 this(即 new Timer() 的实例)
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 词法 this 指向 Timer 实例
console.log(this.seconds); // 1, 2, 3 ...
}, 1000);
}
【代码注释】 箭头函数的核心价值:词法 this 捕获——定义时绑定外层 this,避免回调/定时器中 this 指向 window 或 undefined 的经典陷阱。
语法简化规则:
// 【代码注释】箭头函数语法糖:参数列表 → => → 表达式或块;无 function 关键字
// 单参数可省略圆括号(0 个或多于 1 个参数必须写括号)
const double = n => n * 2; // 单表达式自动作为返回值,等价于 return n * 2
const add = (a, b) => a + b; // 多参数必须 (a, b)
// 多条语句必须用 {} 包裹,且需显式 return(否则返回 undefined)
const clamp = (val, min, max) => {
if (val < min) return min;
if (val > max) return max;
return val;
};
// 返回对象字面量时,外层加 (),否则 { 会被解析为函数体块而非对象
const toPoint = (x, y) => ({ x, y }); // 等价于 return { x: x, y: y }
【代码注释】 箭头函数语法糖规则:单参数可省括号、单表达式可省 return、直接返回对象字面量须用 () 包裹(否则 {} 被解析为函数体)。
3.5.2 箭头函数的限制
| 特性 | 传统函数 | 箭头函数 |
|---|---|---|
this 绑定 |
动态(调用时决定) | 词法(定义时决定) |
arguments 对象 |
✅ 有 | ❌ 无(用 ...args 替代) |
| 作为构造函数 | ✅ 可以 new |
❌ 不可以 |
prototype 属性 |
✅ 有 | ❌ 无 |
generator 函数 |
✅ 可以 | ❌ 不可以 |
// 【代码注释】何时不用箭头函数:需要动态 this、构造函数、generator、依赖 arguments 时
const counter = {
count: 0,
// ❌ 对象字面量中的箭头方法:this 指向定义时外层(通常是全局),不是 counter
increment: () => { counter.count++; }, // 只能硬编码 counter,无法通用复用
// ✅ ES6 方法简写:调用时 this 绑定为 counter
decrement() { this.count--; }
};
// DOM 事件:浏览器以「元素」为 this 调用监听器;箭头函数会丢失 element 引用
button.addEventListener('click', function () {
this.classList.toggle('active'); // this === 被点击的 button
});
// 若必须用箭头函数,应改用 event.currentTarget 代替 this
【代码注释】 箭头函数四大限制:无自身 this、无 arguments、不能 new、无 prototype。对象方法和 DOM 事件处理仍需传统函数,避免 this 错误。
3.5.3 经典应用场景
// 【代码注释】场景一:声明式数组管道 —— 每步返回新数组/值,配合箭头函数可读性高
const orders = [
{ id: 1, amount: 200, status: 'paid' },
{ id: 2, amount: 500, status: 'pending' },
{ id: 3, amount: 150, status: 'paid' },
];
const paidTotal = orders
.filter(o => o.status === 'paid') // 谓词筛选,保留 paid 订单
.map(o => o.amount) // 投影为 number 数组
.reduce((sum, amt) => sum + amt, 0); // 累加器初始值 0,避免空数组得到 undefined
console.log('已支付总额:', paidTotal); // 350
// 【代码注释】场景二:Promise 链中若写 function(){ this.token=... },this 会丢失
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
this.token = null;
}
login(credentials) {
return fetch(`${this.baseURL}/login`, {
method: 'POST',
body: JSON.stringify(credentials)
})
.then(res => res.json())
.then(data => {
this.token = data.token; // 箭头函数捕获类方法调用时的 this
return data.user;
});
}
}
// 【代码注释】场景三:柯里化 —— 先固定 factor,再得到一元函数,便于复用与组合
const multiply = factor => value => value * factor;
const double = multiply(2); // 闭包保存 factor=2
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
【代码注释】 三大经典应用:链式数组操作(filter/map/reduce)代码极简;Promise 链避免 this 丢失;柯里化函数工厂(multiply(2) 返回 double)。
3.6 模板字符串(Template Literals)
3.6.1 核心语法与特性
模板字符串使用反引号(`)定义,支持多行文本和内嵌表达式,彻底取代了繁琐的字符串拼接。
// 【代码注释】模板字符串(Template Literals):反引号 ` 包裹,${} 内为运行时求值的表达式
const user = { name: '张三', age: 28, vip: true };
// ❌ + 拼接:类型隐式转换、括号层级难维护、换行需 \n
const greetingOld = '你好,' + user.name + '!' + (user.vip ? '(VIP)' : '') + '\n年龄:' + user.age;
// ✅ 模板字符串:插值与换行原生支持,逻辑与展示结构一致
const greeting = `你好,${user.name}!${user.vip ? '(VIP)' : ''}
年龄:${user.age}`;
const price = 299;
const tax = 0.08;
// ${} 内可写任意表达式,包括方法调用;结果会 toString 后嵌入
console.log(`含税价格:${(price * (1 + tax)).toFixed(2)} 元`);
// 反引号字符串保留源码中的换行与行首空格;.trim() 去掉首尾空白行,常用于 HTML/SQL 片段
const html = `
<div class="card">
<h2>${user.name}</h2>
<p>年龄:${user.age}</p>
</div>
`.trim();
【代码注释】 模板字符串支持多行与表达式插值,可读性远超字符串拼接。${expr} 中可写任意表达式;.trim() 消除模板首尾空白行,常用于生成 HTML 片段。
3.6.2 标签模板(Tagged Templates)
标签模板是模板字符串的高级用法,允许用函数解析模板——这是许多库(如 styled-components、graphql、sql 模板)的底层机制。
// 【代码注释】Tagged Template:tag`...${a}...${b}` 等价于 tag(['...', '...'], a, b)
// strings:由静态文本分割得到的字符串数组;values:各 ${} 求值后的结果(rest 收集)
function highlight(strings, ...values) {
// strings.length === values.length + 1(首尾常有纯静态片段)
return strings.reduce((result, str, i) => {
const value = values[i] !== undefined
? `<mark>${values[i]}</mark>` // 对每个插值包裹高亮标签
: '';
return result + str + value;
}, '');
}
const name = '张三';
const score = 98;
const message = highlight`学员 ${name} 本次考试得了 ${score} 分,优秀!`;
// 【代码注释】标签模板可做「编译期/运行期」字符串处理:转义、国际化、样式组件等
function safeSQL(strings, ...values) {
const escaped = values.map(v =>
typeof v === 'string' ? v.replace(/'/g, "''") : v // SQL 单引号转义示意
);
return strings.reduce((q, s, i) => q + s + (escaped[i] ?? ''), '');
}
const userId = "1 OR 1=1";
const query = safeSQL`SELECT * FROM users WHERE id = '${userId}'`;
// 生产环境应使用参数化查询(Prepared Statement),此处仅演示标签函数机制
【代码注释】 标签模板(Tagged Template):函数名紧接反引号,第一参数为字符串片段数组,其余为插值。styled-components、gql、sql 等库均基于此机制。
3.7 解构赋值(Destructuring Assignment)
3.7.1 数组解构
原理:按位置从可迭代对象中提取值并绑定到变量。
// 【代码注释】数组解构:按索引位置一一绑定,右侧须为可迭代对象(数组、可迭代类数组)
const [first, second, third] = [10, 20, 30]; // 长度不足时,多余变量为 undefined
// 留空位跳过中间元素,只取第三个
const [, , z] = [1, 2, 3];
// 默认值仅在对应位置严格等于 undefined 时生效(null 不会触发默认值)
const [a = 0, b = 0] = [5];
console.log(a, b); // 5 0
// 解构赋值可交换变量,右侧先求值再赋值,无需临时变量
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
// rest 必须写在解构模式最后一位,得到真数组
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
function minMax(arr) {
return [Math.min(...arr), Math.max(...arr)]; // 用元组风格返回多值
}
const [min, max] = minMax([3, 1, 4, 1, 5, 9]);
console.log(`最小值 ${min},最大值 ${max}`);
【代码注释】 数组解构按索引位置匹配:可跳过元素(,)、设默认值、配合 ...rest 收集剩余项、交换变量(无需临时变量),函数返回多值时解构最为优雅。
3.7.2 对象解构
// 【代码注释】对象解构:按属性名匹配,与属性顺序无关
const user = {
id: 42,
name: '李四',
address: { city: '上海', district: '浦东' },
roles: ['admin', 'editor']
};
const { id, name } = user; // 变量名须与属性名一致(或用重命名语法)
// { 原属性名: 新变量名 } —— 从 user.name 读到 userName
const { name: userName, id: userId } = user;
const { email = 'unknown@example.com' } = user; // user 无 email 时用默认
// 嵌套:先匹配 address,再从中解构 city、district
const { address: { city, district } } = user;
// rest 收集除 uid 外其余可枚举自有属性(浅拷贝一份新对象)
const { id: uid, ...rest } = user;
// 参数解构 + 嵌套默认值:address 缺失时用 {} 避免解构 undefined 报错
function renderUser({ name, email = '无', address: { city } = {} }) {
return `${name}(${email})- ${city}`;
}
renderUser(user);
【代码注释】 对象解构按属性名匹配:冒号后是新变量名(重命名),可设默认值,支持嵌套解构 { address: { city } },函数参数解构是最常见的实战场景。
3.7.3 经典应用场景
// 【代码注释】场景一:深层 API 响应一次解构到位,避免 response.data.data.user 链式取值
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
const { data: { user, permissions }, meta: { total } } = await response.json();
return { user, permissions, total };
}
// 【代码注释】场景二:组件入参解构 = 自文档化 props API + 默认值(React/Vue 均常用)
function UserCard({ name, avatar, bio = '这个人很懒,什么都没留下', onClick }) {
return `<div onclick="${onClick}">${name}: ${bio}</div>`;
}
// 【代码注释】场景三:Map 迭代时 [key, value] 解构比 entry[0]/entry[1] 更清晰
const scores = new Map([['张三', 90], ['李四', 85], ['王五', 92]]);
for (const [name, score] of scores) {
console.log(`${name}:${score} 分`);
}
// 【代码注释】场景四:从模块按需解构子 API,利于 Tree Shaking 与避免 import * 整包
const { readFile, writeFile } = require('fs').promises;
import { ref, reactive, computed } from 'vue';
【代码注释】 解构的实战价值:减少临时变量、在函数签名处声明「需要哪些字段」、与 Map/for…of/import 组合时代码意图更直观;嵌套过深时建议配合可选链 ?. 或分步解构以提升可读性。
3.8 展开运算符与 Rest 参数
3.8.1 展开运算符(Spread ...)
展开运算符将可迭代对象"展开"为离散的元素,用于数组/对象的创建、合并、传参。
// 【代码注释】展开运算符 ...:在数组/对象/函数调用中「展开」可迭代对象或自有属性
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // 合并为新数组,不修改原数组
const copy = [...arr1]; // 浅拷贝:第一层元素引用仍与原数组共享
const divs = document.querySelectorAll('div');
const divArray = [...divs]; // 任意可迭代对象 → 真数组,从而可用 map/filter/reduce
const defaults = { theme: 'light', lang: 'zh-CN', fontSize: 14 };
const userPrefs = { theme: 'dark', fontSize: 16 };
const config = { ...defaults, ...userPrefs };
// 同名键后者覆盖前者;仅拷贝可枚举自有属性,不含原型链
// 【代码注释】不可变更新:每次返回新引用,便于 React diff / 时间旅行调试
const state = { user: { name: '张三', age: 25 }, count: 0 };
const newState = {
...state,
count: state.count + 1,
user: { ...state.user, age: 26 } // 嵌套层也需展开,否则仍共享 state.user 引用
};
const numbers = [3, 1, 4, 1, 5, 9];
console.log(Math.max(...numbers)); // 展开为离散参数,等价 Math.max(3, 1, 4, ...)
【代码注释】 展开运算符三大用途:数组/对象浅合并({ ...defaults, ...userPrefs })、不可变更新(Redux 核心模式)、类数组转真数组([...nodeList])。
3.8.2 Rest 参数(...args)
Rest 参数收集函数调用时剩余的实参,必须是参数列表中的最后一个,得到的是真正的数组。
// 【代码注释】Rest 参数:形式为 ...name,必须是形参列表最后一项,得到 Array 实例
function sum(first, ...rest) {
// first 绑定第一个实参,其余进入 rest 数组
return rest.reduce((acc, n) => acc + n, first);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
function logAll(label, ...items) {
// 对比 arguments:非数组、含命名参数之外信息;箭头函数无 arguments
items.forEach((item, i) => console.log(`${label}[${i}]:`, item));
}
// 【代码注释】装饰器模式:包装 fn 时保留其任意 arity
function withLogging(fn) {
return function (...args) {
console.log('调用参数:', args);
const result = fn(...args); // 再次展开,还原为 fn(a, b, ...) 调用形式
console.log('返回值:', result);
return result;
};
}
const loggedAdd = withLogging((a, b) => a + b);
loggedAdd(3, 4);
【代码注释】 Rest 参数收集剩余实参,得到真正的 Array(可用 map/filter),优于 arguments 伪数组。高阶函数透传参数时 ...args + fn(...args) 是标准写法。
3.9 ES6 模块系统(Modules)
3.9.1 模块化的意义
在模块化出现之前,JavaScript 所有文件共享全局作用域,大型项目极易发生命名冲突,依赖顺序也难以管理。ES6 模块(ESM)提供了文件级别的作用域和静态的导入导出分析,使工具链可以做 Tree Shaking(摇树优化)。
3.9.2 导出(export)
// math.js —— ES Module 在文件顶层静态分析 import/export,利于 Tree Shaking
// 【代码注释】具名导出:导入方必须使用相同导出名(或用 as 重命名),可导出任意多个
export const PI = 3.14159265;
export function add(a, b) { return a + b; }
export class Vector {
constructor(x, y) { this.x = x; this.y = y; }
magnitude() { return Math.sqrt(this.x ** 2 + this.y ** 2); }
}
// 先声明再导出,适合隐藏未 export 的模块内部实现
const _secret = 'internal'; // 未 export,其他文件无法 import
function helper() { return 42; }
export { helper as utilHelper }; // 对外名称 utilHelper
// 【代码注释】默认导出:一个模块最多一个 default,导入时可随意命名
export default class Calculator {
add(a, b) { return a + b; }
sub(a, b) { return a - b; }
}
【代码注释】 ES6 模块导出:具名导出(花括号)可多个;默认导出每模块唯一;导出时可重命名(as);Object.freeze 等声明后统一导出保持文件整洁。
3.9.3 导入(import)
// app.js —— 浏览器须 <script type="module">;Node 需 "type":"module" 或 .mjs
// 具名导入:花括号内名称须与导出一致(编译期静态绑定)
import { PI, add, Vector } from './math.js';
import { utilHelper as helper } from './math.js'; // 本地别名,避免与当前文件变量冲突
import Calculator from './math.js'; // default 导入名可自定,常见写法与类名一致
import Calculator, { PI, add } from './math.js'; // 混合导入:default 写最前
import * as MathLib from './math.js'; // 命名空间对象:仅包含 export 的成员
console.log(MathLib.PI);
console.log(MathLib.add(1, 2));
// 【代码注释】动态 import():返回 Promise,可在运行时按条件加载(路由懒加载、CDN 分包)
async function loadModule() {
const { add } = await import('./math.js');
console.log(add(3, 4));
}
【代码注释】 模块导入:具名导入须花括号,名称必须匹配;默认导入名称自定义;* as Lib 命名空间导入;import() 动态导入返回 Promise,适合路由懒加载。
3.9.4 ESM vs CommonJS 对比
| 特性 | ES Modules(ESM) | CommonJS(CJS) |
|---|---|---|
| 语法 | import / export |
require / module.exports |
| 分析时机 | 静态(编译时) | 动态(运行时) |
| Tree Shaking | ✅ 支持 | ❌ 不支持 |
顶层 await |
✅ 支持 | ❌ 不支持 |
| 适用环境 | 浏览器原生 + Node.js(.mjs) | Node.js(传统) |
| 循环依赖 | 支持(导出 live binding) | 支持(但可能得到不完整对象) |
// 【代码注释】ESM Live Binding:导入的 let/const 绑定到导出模块的同一绑定,非快照拷贝
// counter.js
export let count = 0;
export function increment() { count++; }
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 —— 无需重新 import,绑定自动反映最新值
// CommonJS:require 得到的是导出瞬间的值拷贝,后续修改不同步
【代码注释】 ESM Live Binding(活绑定):import { count } 拿到的是原模块变量的实时引用,increment() 后 count 自动更新。CJS require 是值拷贝,二者行为截然不同。
3.10 ES6 Class 类系统
3.10.1 Class 核心语法与原型关系
ES6 的 class 语法是原型继承的语法糖,底层仍然基于原型链(prototype),但语义更清晰、更接近传统面向对象。
// 【代码注释】class 是构造函数的语法糖,typeof Person === 'function'
// constructor 即原来的构造函数,方法写在 class 体内会自动挂到 prototype 上
class Person {
// 公有实例字段(ES2022,class fields 提案)
// 声明在 class 顶层,等价于在 constructor 中 this.name = name
name;
age;
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法 —— 挂到 Person.prototype,所有实例共享一份,节省内存
greet() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
}
// 静态方法 —— 挂到 Person 本身,实例无法访问,适合工厂/工具函数
static create(name, age) {
return new Person(name, age);
}
// getter/setter —— 访问器属性,实例用法:p.info、p.age = 30
get info() {
return `${this.name}(${this.age})`;
}
set age(val) {
if (val < 0 || val > 150) throw new RangeError('年龄不合法');
this._age = val;
}
get age() {
return this._age;
}
}
const p = Person.create('张三', 25);
console.log(p.greet()); // 你好,我是 张三,今年 25 岁。
console.log(Person.prototype.greet === p.greet); // true —— 方法共享于原型
console.log(typeof Person); // 'function'
【代码注释】 class 是构造函数的语法糖:类方法写在原型上(所有实例共享),static 写在类本身,constructor 是构造入口。用 class 而非手写原型链,代码可读性与可维护性大幅提升。
3.10.2 继承(extends / super)
// 【代码注释】extends:子类继承父类所有实例方法和静态方法
// super() 必须在子类 constructor 的 this 使用之前调用,否则 ReferenceError
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} 发出了声音。`;
}
static kingdom() {
return '动物界';
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 先调用父类 constructor,初始化 this.name
this.breed = breed; // 再添加子类独有属性
}
// 方法覆盖(Override):子类同名方法遮蔽父类,可用 super.speak() 调用父类版本
speak() {
return `${super.speak()} 汪汪!`;
}
// 子类新方法
fetch() {
return `${this.name}(${this.breed})正在捡球...`;
}
}
const dog = new Dog('旺财', '柴犬');
console.log(dog.speak()); // 旺财 发出了声音。 汪汪!
console.log(Dog.kingdom()); // 动物界 —— 静态方法也可继承
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true —— 原型链包含 Animal.prototype
【代码注释】 extends 同时建立实例链(Dog.prototype → Animal.prototype)和静态链(Dog → Animal);super() 调用父类构造函数;super.method() 访问父类方法。经典用途:框架自定义错误类、React 组件类。
3.10.3 私有字段(#)与静态私有
// 【代码注释】私有字段(ES2022):# 前缀,严格限于类内部访问,类外访问 SyntaxError
class BankAccount {
#balance = 0; // 私有实例字段,外部无法读写
#transactionLog = []; // 每个实例都有独立的日志数组
static #interestRate = 0.035; // 静态私有字段,整个类共享
constructor(initialBalance) {
this.#balance = initialBalance;
this.#log('开户', initialBalance);
}
deposit(amount) {
if (amount <= 0) throw new Error('存款金额须为正数');
this.#balance += amount;
this.#log('存款', amount);
return this; // 支持链式调用
}
withdraw(amount) {
if (amount > this.#balance) throw new Error('余额不足');
this.#balance -= amount;
this.#log('取款', amount);
return this;
}
// 公有 getter 只读暴露余额,外部无法 account.#balance = 999
get balance() {
return this.#balance;
}
get statement() {
return this.#transactionLog.slice(); // 返回副本,防止外部修改日志
}
// 私有方法:只在类内部使用,外部不可调用
#log(type, amount) {
this.#transactionLog.push({
type,
amount,
balance: this.#balance,
time: new Date().toLocaleTimeString()
});
}
static calculateAnnualReturn(principal) {
return principal * BankAccount.#interestRate; // 类内可访问静态私有字段
}
}
const account = new BankAccount(1000);
account.deposit(500).withdraw(200); // 链式调用
console.log(account.balance); // 1300
console.log(account.statement); // 交易记录数组
// account.#balance = 99999; // SyntaxError(私有字段)
【代码注释】 私有字段(#)是语言级封装:不是约定俗成的 _ 前缀,而是真正的访问控制,外部访问 SyntaxError。适合银行账户、密码哈希、内部计数器等不应暴露的敏感状态。
3.10.4 Mixin 模式(多继承模拟)
// 【代码注释】JavaScript 单继承限制:extends 只能一个父类。Mixin 用函数返回类的技巧绕过此限制
// Mixin:接受基类,返回扩展后的新类
const Serializable = (Base) => class extends Base {
serialize() {
return JSON.stringify(this);
}
static deserialize(json) {
return Object.assign(new this(), JSON.parse(json));
}
};
const Validatable = (Base) => class extends Base {
validate() {
const errors = [];
for (const [field, rule] of Object.entries(this.constructor.validationRules || {})) {
if (rule.required && !this[field]) {
errors.push(`${field} 是必填项`);
}
if (rule.minLength && this[field]?.length < rule.minLength) {
errors.push(`${field} 最少 ${rule.minLength} 个字符`);
}
}
return { valid: errors.length === 0, errors };
}
};
class BaseModel {
constructor(data = {}) {
Object.assign(this, data);
}
}
// 组合多个 Mixin —— 从右到左叠加 extends
class User extends Serializable(Validatable(BaseModel)) {
static validationRules = {
name: { required: true, minLength: 2 },
email: { required: true }
};
}
const user = new User({ name: '张三', email: 'zs@example.com' });
console.log(user.validate()); // { valid: true, errors: [] }
console.log(user.serialize()); // '{"name":"张三","email":"zs@example.com"}'
const badUser = new User({ name: 'A' });
console.log(badUser.validate()); // { valid: false, errors: ['email 是必填项', 'name 最少 2 个字符'] }
【代码注释】 Mixin 模式解决 JS 单继承局限:每个 Mixin 是「接受基类,返回扩展类」的高阶函数,可组合 N 个能力而不影响原有继承链。Vue 2 mixins 选项、React withXxx HOC 都是变体形式。
3.11 Iterator 与 Generator
3.11.1 可迭代协议(Iterable Protocol)
名词解析:
- Iterator(迭代器):有
next()方法的对象,每次调用返回{ value, done } - Iterable(可迭代对象):有
[Symbol.iterator]()方法的对象,for...of会自动调用它
// 【代码注释】可迭代协议:实现 Symbol.iterator 即可接入 for...of / 展开 / 解构 / Array.from
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
// 实现迭代器工厂:每次 for...of 调用此方法得到新迭代器
[Symbol.iterator]() {
let current = this.start;
const { end, step } = this;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true }; // done:true 告知循环结束
}
};
}
}
const range = new Range(1, 10, 2);
for (const n of range) {
console.log(n); // 1 3 5 7 9
}
console.log([...range]); // [1, 3, 5, 7, 9]
const [first, second] = range; // 解构也走迭代协议
console.log(first, second); // 1 3
console.log(Array.from(range)); // [1, 3, 5, 7, 9]
【代码注释】 实现 [Symbol.iterator]() 后,该类的实例可用于 for...of、展开 ...、解构、Array.from。自定义迭代器的典型应用:分页游标、树形遍历、无限数列。
3.11.2 Generator 函数
// 【代码注释】Generator 函数(function*):每次执行到 yield 暂停,next() 恢复执行
// 返回值是一个同时满足迭代器和可迭代协议的对象
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr; // 暂停,输出 curr;调用 next() 从此处恢复
[prev, curr] = [curr, prev + curr];
}
}
// 取前 10 个斐波那契数
const fib = fibonacci();
const first10 = Array.from({ length: 10 }, () => fib.next().value);
console.log(first10); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
// 【代码注释】有限 Generator:自动结束,done:true 后续 next() 均返回 undefined
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
for (const n of range(0, 10, 3)) {
console.log(n); // 0 3 6 9
}
// 【代码注释】yield* 委托给另一个可迭代对象(Generator 或数组)
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // 递归展开,等价于对子数组逐一 yield
} else {
yield item;
}
}
}
console.log([...flatten([1, [2, [3, [4]]], 5])]); // [1, 2, 3, 4, 5]
【代码注释】 Generator 函数的核心价值:惰性求值(按需生成,不提前计算全部结果),适合无限数列、大数据集逐行处理、协程式异步控制流(早期 co 库)。
3.11.3 Generator 经典应用场景
// 【代码注释】场景一:分页游标 —— 每次请求只在调用 next() 时发出,天然惰性
async function* paginate(url, pageSize = 20) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}&size=${pageSize}`);
const { data, total } = await response.json();
yield data; // 每次 yield 一页数据
hasMore = page * pageSize < total;
page++;
}
}
// 调用方代码(异步 for...of)
async function loadAllUsers() {
const allUsers = [];
for await (const page of paginate('/api/users')) {
allUsers.push(...page);
console.log(`已加载 ${allUsers.length} 个用户`);
}
return allUsers;
}
// 【代码注释】场景二:ID 生成器 —— 每个模块独立维护自增序列,无全局状态污染
function* idGenerator(prefix = 'ID') {
let id = 1;
while (true) {
yield `${prefix}-${String(id++).padStart(6, '0')}`;
}
}
const userIdGen = idGenerator('USER');
const orderIdGen = idGenerator('ORDER');
console.log(userIdGen.next().value); // USER-000001
console.log(userIdGen.next().value); // USER-000002
console.log(orderIdGen.next().value); // ORDER-000001
// 【代码注释】场景三:状态机 —— 每个 yield 是一个状态节点,next(input) 传入触发条件
function* trafficLight() {
while (true) {
yield '🔴 红灯(停车)';
yield '🟡 黄灯(准备)';
yield '🟢 绿灯(通行)';
}
}
const light = trafficLight();
setInterval(() => {
console.log(light.next().value);
}, 2000); // 每 2s 切换一次灯
【代码注释】 Generator 三大应用:分页游标(异步惰性加载)、唯一 ID 生成器(闭包维护状态)、有限状态机(每个 yield 即一个状态)。配合 async function* + for await...of 实现异步迭代流。
3.12 Proxy 与 Reflect
3.12.1 Proxy 基本用法
名词解析:
- Proxy(代理):拦截对目标对象的操作(读/写/删除/函数调用等),可插入自定义逻辑
- Reflect:提供与 Proxy 陷阱一一对应的默认行为方法,确保标准操作不被遗漏
// 【代码注释】Proxy(target, handler):target 是被代理对象,handler 定义拦截行为(陷阱 trap)
const user = { name: '张三', age: 25, _password: 'secret' };
const safeUser = new Proxy(user, {
// get 陷阱:读取属性时触发
get(target, prop, receiver) {
if (prop.startsWith('_')) {
throw new Error(`禁止访问私有属性:${prop}`);
}
console.log(`[读取] ${prop}`);
return Reflect.get(target, prop, receiver); // 用 Reflect 执行默认读取
},
// set 陷阱:赋值时触发
set(target, prop, value, receiver) {
if (prop === 'age') {
if (typeof value !== 'number' || value < 0 || value > 150) {
throw new TypeError(`age 值非法:${value}`);
}
}
console.log(`[写入] ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver); // 返回 true 表示成功
},
// has 陷阱:in 运算符触发
has(target, prop) {
if (prop.startsWith('_')) return false; // 屏蔽私有属性的存在
return Reflect.has(target, prop);
},
// deleteProperty 陷阱
deleteProperty(target, prop) {
if (prop.startsWith('_')) {
throw new Error(`禁止删除私有属性:${prop}`);
}
return Reflect.deleteProperty(target, prop);
}
});
console.log(safeUser.name); // [读取] name → 张三
safeUser.age = 30; // [写入] age = 30
// safeUser._password; // Error: 禁止访问私有属性:_password
// safeUser.age = -1; // TypeError: age 值非法
console.log('_password' in safeUser); // false(has 陷阱屏蔽)
【代码注释】 Proxy 的核心是透明拦截:配合 Reflect 保留默认行为,在之上叠加校验/日志/访问控制。Vue 3 响应式系统(reactive)正是基于 Proxy + Reflect,取代了 Vue 2 的 Object.defineProperty。
3.12.2 Proxy 实战:响应式数据
// 【代码注释】简版响应式系统:track(依赖收集)+ trigger(触发更新),模拟 Vue 3 核心机制
function reactive(target) {
const listeners = new Map(); // prop → Set<callback>
return new Proxy(target, {
get(obj, prop) {
// 依赖收集:当前正在执行的 effect 订阅此属性
if (currentEffect) {
if (!listeners.has(prop)) listeners.set(prop, new Set());
listeners.get(prop).add(currentEffect);
}
const value = Reflect.get(obj, prop);
// 嵌套对象递归代理(Vue 3 lazy 实现)
return typeof value === 'object' && value !== null
? reactive(value)
: value;
},
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
// 触发所有订阅此属性的 effect
if (listeners.has(prop)) {
listeners.get(prop).forEach(fn => fn());
}
return result;
}
});
}
let currentEffect = null;
function effect(fn) {
currentEffect = fn;
fn(); // 首次执行收集依赖
currentEffect = null;
}
// 使用示例
const state = reactive({ count: 0, name: '张三' });
effect(() => {
console.log(`[视图更新] count = ${state.count}`); // 订阅 count
});
state.count++; // → [视图更新] count = 1
state.count++; // → [视图更新] count = 2
state.name = '李四'; // 无输出(effect 只订阅了 count)
【代码注释】 这 40 行代码还原了 Vue 3 reactive + effect 的核心思路:get 收集当前 effect 为依赖,set 触发相关 effect 重跑。理解此模式后再看 Vue 3 源码会豁然开朗。
3.12.3 Proxy 其他常见应用
// 【代码注释】应用一:验证对象(表单数据入口)
function createValidatedObject(validationSchema) {
return new Proxy({}, {
set(target, prop, value) {
const rule = validationSchema[prop];
if (rule) {
if (rule.type && typeof value !== rule.type) {
throw new TypeError(`${prop} 必须是 ${rule.type} 类型`);
}
if (rule.min !== undefined && value < rule.min) {
throw new RangeError(`${prop} 不能小于 ${rule.min}`);
}
if (rule.max !== undefined && value > rule.max) {
throw new RangeError(`${prop} 不能大于 ${rule.max}`);
}
if (rule.pattern && !rule.pattern.test(value)) {
throw new Error(`${prop} 格式不正确`);
}
}
return Reflect.set(target, prop, value);
}
});
}
const userForm = createValidatedObject({
age: { type: 'number', min: 0, max: 150 },
email: { type: 'string', pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
name: { type: 'string' }
});
userForm.name = '张三'; // ✅
userForm.email = 'zs@example.com'; // ✅
// userForm.age = -1; // RangeError
// userForm.email = 'invalid-email'; // Error: email 格式不正确
// 【代码注释】应用二:函数调用日志(函数 Proxy,apply 陷阱)
function withCallLog(fn, name = fn.name) {
return new Proxy(fn, {
apply(target, thisArg, args) {
const start = performance.now();
const result = Reflect.apply(target, thisArg, args);
const cost = (performance.now() - start).toFixed(2);
console.log(`[${name}] 参数:`, args, '耗时:', `${cost}ms`, '返回:', result);
return result;
}
});
}
const slowAdd = withCallLog((a, b) => {
// 模拟耗时
const end = Date.now() + 10;
while (Date.now() < end) {}
return a + b;
}, 'slowAdd');
slowAdd(3, 4); // [slowAdd] 参数: [3, 4] 耗时: ~10ms 返回: 7
【代码注释】 Proxy 的两大高频场景:数据验证(set 陷阱做入参校验,替代手写 setter)和函数增强(apply 陷阱做 AOP 日志/计时),均无侵入性,不修改原对象/函数。
3.13 可选链与空值合并
3.13.1 可选链(Optional Chaining ?.)
// 【代码注释】可选链 ?. :短路求值,左侧为 null/undefined 时直接返回 undefined,不抛错
const user = {
name: '张三',
address: {
city: '北京',
geo: { lat: 39.9, lng: 116.4 }
},
// phone: undefined // 字段不存在
};
// ❌ 旧写法:冗余的防护链(易遗漏导致 Cannot read property of undefined)
const lat = user && user.address && user.address.geo && user.address.geo.lat;
// ✅ 可选链:任一环节为 null/undefined 则整体返回 undefined
const lat2 = user?.address?.geo?.lat; // 39.9
const phone = user?.phone?.number; // undefined(不抛错)
const zipCode = user?.address?.zipCode; // undefined
// 可选下标访问:数组或 null 的动态下标
const firstTag = user?.tags?.[0]; // undefined
const result = user?.tags?.find(t => t.id === 1); // undefined
// 可选方法调用
const formatted = user?.getFullName?.(); // undefined(方法不存在)
const upperName = user?.name?.toUpperCase(); // '张三'
// 【代码注释】结合解构与默认值
const { address: { city = '未知城市' } = {} } = user ?? {};
【代码注释】 可选链 ?. 大幅减少防御式判断代码,尤其适合深层嵌套的 API 响应解析、DOM 属性访问、可选回调调用。注意:?. 只对 null/undefined 短路,0、''、false 不触发。
3.13.2 空值合并(Nullish Coalescing ??)
// 【代码注释】?? 与 || 的关键区别:
// || falsy 即回退(含 0、''、false、NaN)
// ?? 仅 null/undefined 回退,保留合法的「假值」
function createConfig(options = {}) {
return {
// ❌ 用 || 时:timeout=0(禁用超时)会被误判为假值而用默认值
timeoutOld: options.timeout || 5000, // 若 timeout=0 → 5000(错误)
// ✅ 用 ?? 时:只在 null/undefined 时回退
timeout: options.timeout ?? 5000, // 若 timeout=0 → 0(正确)
debug: options.debug ?? false, // false 保留
maxRetries: options.maxRetries ?? 3,
// 可选链 + 空值合并组合:从深层取值,取不到时给默认值
logLevel: options?.logger?.level ?? 'info'
};
}
console.log(createConfig({ timeout: 0, debug: false }));
// { timeout: 0, debug: false, maxRetries: 3, logLevel: 'info' }
// 【代码注释】逻辑赋值运算符(ES2021)—— 惰性赋值,仅在条件满足时执行赋值
let a = null;
let b = '';
let c = 0;
a ??= '默认值'; // 等价 a = a ?? '默认值':a 为 null → 赋值
b ||= '备用值'; // 等价 b = b || '备用值':'' 为 falsy → 赋值
c &&= c * 2; // 等价 c = c && c * 2:0 为 falsy → 不赋值
console.log(a, b, c); // '默认值' '备用值' 0
// 【代码注释】真实场景:组件默认 props 处理
function initComponent(props) {
// 仅当 props 中真正没有该字段(null/undefined)才使用默认值
props.theme ??= 'light';
props.pageSize ??= 20;
props.disabled ??= false; // props.disabled = false 时不会被覆盖
return props;
}
【代码注释】 ?? 与 || 是不同问题的工具:|| 用于「有值就用,空就回退」,?? 用于「明确缺失(null/undefined)才回退」。在处理数字 0、空字符串 ''、false 等合法假值时必须用 ??,否则会产生难以察觉的 bug。
4. 数组操作深度解析
4.1 数组扁平化技术
名词解析:数组扁平化(Array Flattening)
数组扁平化是指将多维嵌套数组转换为一维数组的过程,也称为数组拉平。
扁平化方法对比:
// 【代码注释】测试数据:混合 number、string、多层嵌套数组,用于对比各扁平化策略的行为差异
const nestedArray = [
[1000, 2000, 3000],
'hello',
[
[10, 20, 30],
[
'a',
'b',
['A', 'B', 'C']
],
'嵌套字符串'
],
12313,
[101, 202, 303]
];
console.log('原始数组:', nestedArray);
// 递归求最大嵌套深度:非数组返回 0,数组为 1 + 子项深度最大值
function getArrayDepth(arr) {
return Array.isArray(arr) ?
1 + Math.max(0, ...arr.map(getArrayDepth)) : 0;
}
console.log('数组维度:', getArrayDepth(nestedArray));
// 【代码注释】方法一:手写递归 —— depth 控制「最多展开几层」,Infinity 表示完全扁平
function flatArrayRecursive(arr, depth = Infinity) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i]) && depth > 0) {
result = result.concat(flatArrayRecursive(arr[i], depth - 1));
} else {
result.push(arr[i]); // 已达深度上限或非数组,原样保留(可能仍为嵌套数组)
}
}
return result;
}
console.log('递归扁平化(无限深度):', flatArrayRecursive(nestedArray));
console.log('递归扁平化(深度2):', flatArrayRecursive(nestedArray, 2));
// 【代码注释】方法二:toString + split —— 依赖 Array#toString 逗号连接,仅适合纯数字/字符串
function flatArrayToString(arr) {
return arr.toString().split(',');
}
console.log('字符串方法扁平化:', flatArrayToString(nestedArray));
// 缺陷:元素变 string;含逗号的字符串会被错误切分;无法指定 depth
// 【代码注释】方法三:Array.prototype.flat(depth) —— ES2019,引擎原生实现,生产首选
function flatArrayNative(arr, depth = Infinity) {
return arr.flat(depth);
}
console.log('原生方法扁平化:', flatArrayNative(nestedArray));
console.log('原生方法扁平化(深度1):', flatArrayNative(nestedArray, 1));
// 【代码注释】方法四:reduce + 递归 —— 函数式风格,语义与手写 for 循环等价
function flatArrayReduce(arr, depth = Infinity) {
return arr.reduce((acc, val) => {
if (Array.isArray(val) && depth > 0) {
return acc.concat(flatArrayReduce(val, depth - 1));
} else {
return acc.concat(val);
}
}, []);
}
console.log('Reduce方法扁平化:', flatArrayReduce(nestedArray));
【代码注释】 扁平化性能对比:原生 flat(Infinity) 利用引擎内部优化,速度最快;递归方法可读性最佳;toString().split(',') 会丢失原始类型,仅适合纯字符串数组。
4.2 扁平化性能对比
性能测试代码:
// 【代码注释】微基准模板:同一输入、多函数对比耗时;注意 JIT 预热,正式测试宜多次取中位数
function performanceTest(methods, testArray) {
const results = {};
methods.forEach(method => {
const start = performance.now();
const result = method.fn(testArray);
const end = performance.now();
results[method.name] = {
time: end - start,
result: result.length
};
});
return results;
}
const largeNestedArray = Array(1000).fill(0).map((_, i) => [
i,
[i * 2, [i * 3, [i * 4]]]
]);
const methods = [
{ name: '递归方法', fn: (arr) => flatArrayRecursive(arr) },
{ name: '原生flat', fn: (arr) => arr.flat(Infinity) },
{ name: 'Reduce方法', fn: (arr) => flatArrayReduce(arr) }
];
const performanceResults = performanceTest(methods, largeNestedArray);
console.log('性能测试结果:', performanceResults);
【代码注释】 performance.now() 精度达微秒级,适合微基准测试;大型嵌套数组(1000 项 × 4 层)下 flat(Infinity) 通常比手写递归快 2~5 倍。
4.3 数组浅拷贝技术
名词解析:浅拷贝(Shallow Copy)
浅拷贝只复制对象或数组的第一层属性,嵌套的对象或数组仍然共享原始引用。
浅拷贝方法详解:
// 【代码注释】浅拷贝:只复制容器第一层;元素若为对象/数组,仍与源共享同一引用
const originalArray = [
{ id: 1, name: '商品A', price: 100 },
{ id: 2, name: '商品B', price: 200 },
{ id: 3, name: '商品C', price: 300 }
];
const originalObject = {
userId: 123,
username: '张三',
profile: { age: 25, city: '北京' },
orders: ['订单1', '订单2']
};
// 方法一:[...arr] / { ...obj } —— ES6 惯用写法,简洁且不改原结构
const arrayCopySpread = [...originalArray];
const objectCopySpread = { ...originalObject };
arrayCopySpread[0].price = 999;
console.log('原始数组受影响:', originalArray[0].price); // 999 —— 嵌套对象未复制
// 方法二:concat() —— 不传参时等价浅拷贝数组;push 只影响新数组长度
const arrayCopyConcat = originalArray.concat();
arrayCopyConcat.push({ id: 4, name: '商品D', price: 400 });
console.log('concat拷贝长度:', arrayCopyConcat.length); // 4
console.log('原数组长度:', originalArray.length); // 3
// 方法三:slice() —— slice(0) 或 slice() 复制整个数组,同样为浅拷贝
const arrayCopySlice = originalArray.slice();
// 方法四:Object.assign({}, src) —— 将 src 自有可枚举属性复制到目标对象
const objectCopyAssign = Object.assign({}, originalObject);
// 与展开区别:assign 会调用目标/源上的 setter;展开是属性描述符级别拷贝
// 方法五:Array.from(iterable, mapFn) —— 可在拷贝同时映射变换
const arrayCopyFrom = Array.from(originalArray);
const mappedCopy = Array.from(originalArray, item => ({
...item, // 对每个元素再浅展开,得到新对象引用
price: item.price * 1.1
}));
【代码注释】 浅拷贝五种方式对比:[...arr] 和 Object.assign 最常用;Array.from 支持 mapper;concat/slice 仅限数组。核心:嵌套引用仍共享,修改深层属性会影响原始数据。
4.4 实战应用:购物车系统
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数组扁平化与拷贝实战</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
}
.section {
margin: 20px 0;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
}
.section h2 {
color: #667eea;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.button {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
transition: all 0.3s;
}
.button:hover {
background: #764ba2;
transform: translateY(-2px);
}
.button:active {
transform: translateY(0);
}
.result {
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-top: 15px;
font-family: 'Courier New', monospace;
white-space: pre-wrap;
max-height: 300px;
overflow-y: auto;
}
.product-item {
background: #fff;
border: 1px solid #ddd;
padding: 10px;
margin: 5px 0;
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.product-info {
flex: 1;
}
.product-price {
color: #e74c3c;
font-weight: bold;
}
.nested-example {
background: #f0f0f0;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>🛒 数组扁平化与拷贝技术实战</h1>
<div class="section">
<h2>📊 多维数组扁平化演示</h2>
<button class="button" onclick="demonstrateFlattening()">演示扁平化</button>
<button class="button" onclick="testFlatteningMethods()">性能测试</button>
<div id="flattenResult" class="result"></div>
</div>
<div class="section">
<h2>🛍️ 购物车浅拷贝演示</h2>
<button class="button" onclick="addToCart()">添加商品</button>
<button class="button" onclick="copyCart()">拷贝购物车</button>
<button class="button" onclick="modifyCopy()">修改拷贝</button>
<button class="button" onclick="compareCarts()">比较购物车</button>
<div id="cartResult"></div>
<div id="cartComparison" class="result"></div>
</div>
<div class="section">
<h2>📈 数据处理管道</h2>
<button class="button" onclick="processData()">处理数据</button>
<div id="pipelineResult" class="result"></div>
</div>
</div>
<script>
// 【代码注释】扁平化演示区:nestedData 为分类+商品列表的二维业务结构(本页未直接用于 flat 按钮)
const nestedData = [
{
category: '电子产品',
items: [
{ name: '手机', price: 2999, specs: ['8GB', '128GB', '5G'] },
{ name: '笔记本', price: 5999, specs: ['16GB', '512GB', 'i7'] }
]
},
{
category: '配件',
items: [
{ name: '耳机', price: 299, specs: ['蓝牙', '降噪'] },
{ name: '充电器', price: 99, specs: ['快充', '多口'] }
]
}
];
// 【代码注释】浅拷贝演示区:每项为对象,拷贝数组后改 item.price 会联动原购物车
let shoppingCart = [
{
id: 1,
name: 'iPhone 15',
price: 5999,
quantity: 1,
specs: { storage: '128GB', color: '蓝色' }
},
{
id: 2,
name: 'AirPods Pro',
price: 1899,
quantity: 2,
specs: { type: '入耳式', noise: '主动降噪' }
}
];
let cartCopy = null;
// 【代码注释】对比 flat(depth):depth 为 1 只展开最外层子数组,Infinity 递归至一维
function demonstrateFlattening() {
const resultDiv = document.getElementById('flattenResult');
const testArray = [
[1, 2, 3],
[[4, 5], [6, 7]],
[[[8, 9]], 10]
];
const results = {
'原始数组': JSON.stringify(testArray, null, 2),
'flat(1) 一层扁平': testArray.flat(1),
'flat(2) 两层扁平': testArray.flat(2),
'flat(Infinity) 完全扁平': testArray.flat(Infinity)
};
resultDiv.textContent = JSON.stringify(results, null, 2);
}
// 【代码注释】微基准:1000 条三层嵌套,对比手写递归 / flat / toString 耗时
function testFlatteningMethods() {
const resultDiv = document.getElementById('flattenResult');
// 创建大型嵌套数组
const largeArray = Array(1000).fill(0).map((_, i) => [
i,
[i * 2, [i * 3]]
]);
const methods = [
{
name: '递归方法',
fn: (arr) => {
const result = [];
function flatten(item) {
if (Array.isArray(item)) {
item.forEach(flatten);
} else {
result.push(item);
}
}
arr.forEach(flatten);
return result;
}
},
{
name: '原生flat',
fn: (arr) => arr.flat(Infinity)
},
{
name: 'toString + split',
fn: (arr) => arr.toString().split(',')
}
];
const performanceData = {};
methods.forEach(method => {
const start = performance.now();
const result = method.fn(largeArray);
const end = performance.now();
performanceData[method.name] = {
执行时间: `${(end - start).toFixed(2)}ms`,
结果长度: result.length,
性能: end - start < 1 ? '优秀' : end - start < 10 ? '良好' : '一般'
};
});
resultDiv.textContent = JSON.stringify(performanceData, null, 2);
}
// 【代码注释】添加商品到购物车
function addToCart() {
const newProduct = {
id: shoppingCart.length + 1,
name: `产品${shoppingCart.length + 1}`,
price: Math.floor(Math.random() * 5000) + 1000,
quantity: 1,
specs: {
storage: '256GB',
color: '随机'
}
};
shoppingCart.push(newProduct);
displayCart();
}
function copyCart() {
cartCopy = [...shoppingCart]; // 新数组,但元素仍是同一批对象引用
alert(`购物车已拷贝!包含 ${cartCopy.length} 个商品`);
}
function modifyCopy() {
if (!cartCopy) {
alert('请先拷贝购物车!');
return;
}
// 修改的是 item 对象本身,shoppingCart 与 cartCopy 指向相同 item
cartCopy.forEach(item => {
item.price = Math.floor(item.price * 0.9);
});
alert('拷贝购物车的所有商品已打9折!(原购物车价格也会变)');
}
function compareCarts() {
if (!cartCopy) {
alert('请先拷贝购物车!');
return;
}
const comparison = {
'原购物车商品': shoppingCart.map(item => ({
name: item.name,
price: item.price
})),
'拷贝购物车商品': cartCopy.map(item => ({
name: item.name,
price: item.price
})),
'说明': '浅拷贝的对象属性共享引用,修改拷贝中的对象属性会影响原数组'
};
document.getElementById('cartComparison').textContent =
JSON.stringify(comparison, null, 2);
}
// 【代码注释】显示购物车内容
function displayCart() {
const cartDiv = document.getElementById('cartResult');
if (shoppingCart.length === 0) {
cartDiv.innerHTML = '<p>购物车为空</p>';
return;
}
const total = shoppingCart.reduce((sum, item) =>
sum + (item.price * item.quantity), 0);
cartDiv.innerHTML = shoppingCart.map(item => `
<div class="product-item">
<div class="product-info">
<strong>${item.name}</strong><br>
<small>规格: ${item.specs.storage} / ${item.specs.color}</small>
</div>
<div class="product-price">
¥${item.price} x ${item.quantity}
</div>
</div>
`).join('') + `
<div style="text-align: right; margin-top: 20px;">
<strong>总计: ¥${total}</strong>
</div>
`;
}
// 【代码注释】数据处理管道演示
function processData() {
const resultDiv = document.getElementById('pipelineResult');
// 模拟API返回的嵌套数据
const apiResponse = {
status: 'success',
data: {
users: [
{
id: 1,
name: '张三',
orders: [
{ orderId: 'A001', amount: 100 },
{ orderId: 'A002', amount: 200 }
]
},
{
id: 2,
name: '李四',
orders: [
{ orderId: 'B001', amount: 150 }
]
}
]
}
};
// flatMap = map + flat(1):每个 user 映射为订单数组后压平为一维列表
const processedData = {
'原始数据': apiResponse,
'提取订单数组': apiResponse.data.users.flatMap(user =>
user.orders.map(order => ({
...order,
userName: user.name,
userId: user.id
}))
),
'订单总金额': apiResponse.data.users
.flatMap(user => user.orders)
.reduce((sum, order) => sum + order.amount, 0)
};
resultDiv.textContent = JSON.stringify(processedData, null, 2);
}
// 初始化显示
displayCart();
</script>
</body>
</html>
【代码注释】 完整可运行 HTML:购物车浅拷贝演示展示「修改拷贝中的对象属性影响原数组」的经典现象;flatMap + reduce 数据管道演示嵌套订单数据的提取与聚合。
5. 对象属性特性详解
5.1 属性描述符架构
名词解析:属性描述符(Property Descriptor)
属性描述符是一个对象,用于描述对象属性的各种特性和配置。它决定了属性的行为方式,包括是否可写、可枚举、可配置等。
属性描述符完整架构:
【代码注释】 属性描述符双轨架构:数据属性(value/writable)存值;访问器属性(get/set)动态计算。二者均受 enumerable/configurable 控制,不可同时指定 value 和 get。
5.2 数据属性详解
数据属性的四大特性:
// 【代码注释】属性描述符:精确控制单个属性的读写、遍历、删除、重定义行为
const product = {
id: 1001,
name: '高端笔记本电脑',
price: 5999,
category: '电子产品'
};
// 普通字面量属性默认为数据描述符:writable/enumerable/configurable 均为 true
const descriptors = Object.getOwnPropertyDescriptors(product);
console.log('product对象的属性描述符:', descriptors);
// 【代码注释】defineProperty 新增或重定义属性;与字面量不同,可显式设为只读/不可枚举
Object.defineProperty(product, 'serialNumber', {
value: 'SN20240115001',
writable: false, // 赋值无效(严格模式 TypeError,非严格静默失败)
enumerable: true, // 会出现在 for...in / Object.keys
configurable: false // 不能 delete,不能再次修改描述符(除 value 在 writable:true 时)
});
product.serialNumber = 'NEW_SERIAL';
console.log('序列号(修改后):', product.serialNumber); // 仍为 SN20240115001
Object.defineProperty(product, 'internalCode', {
value: 'INT-12345',
writable: true,
enumerable: false, // 对 keys/for...in 隐藏,常用于内部状态字段
configurable: true
});
console.log('可枚举属性:', Object.keys(product));
console.log('所有自有属性名:', Object.getOwnPropertyNames(product)); // 含 internalCode
【代码注释】 Object.defineProperty 精确控制属性行为:writable:false 只读;enumerable:false 隐藏于 Object.keys/for...in;configurable:false 防删防重配。Vue 2 响应式正基于此机制。
5.3 访问器属性实战应用
访问器属性的高级应用场景:
// 【代码注释】访问器属性(Accessor):对外暴露「属性」语法,内部用 _ 前缀字段存真实数据
class SmartProduct {
constructor(basePrice, discountRate = 0) {
this._basePrice = basePrice;
this._discountRate = discountRate;
this._salesCount = 0;
}
// getter:无参,每次访问时按当前 _discountRate 重新计算,避免维护冗余字段
get finalPrice() {
const discount = this._basePrice * (this._discountRate / 100);
return Math.round(this._basePrice - discount);
}
// setter:赋值 laptop.discountRate = x 时触发,可做范围校验(业务不变式)
set discountRate(rate) {
if (rate < 0 || rate > 100) {
throw new Error('折扣率必须在0-100之间');
}
this._discountRate = rate;
}
get discountRate() {
return this._discountRate;
}
get dynamicPrice() {
const bulkDiscount = Math.min(this._salesCount * 0.01, 0.2);
return Math.round(this.finalPrice * (1 - bulkDiscount));
}
recordSale() {
this._salesCount++;
}
// 只读聚合视图:无对应 setter,外部无法 productInfo = {...}
get productInfo() {
return {
basePrice: this._basePrice,
finalPrice: this.finalPrice,
discountRate: this._discountRate,
salesCount: this._salesCount,
dynamicPrice: this.dynamicPrice
};
}
}
const laptop = new SmartProduct(5999, 10);
console.log('产品信息:', laptop.productInfo);
laptop.recordSale();
laptop.recordSale();
console.log('销售2次后的动态价格:', laptop.dynamicPrice);
laptop.discountRate = 15; // 走 setter,而非直接改 _discountRate
console.log('调整折扣率后的价格:', laptop.finalPrice);
【代码注释】 访问器属性实战:get finalPrice 每次访问时动态计算,无需手动维护派生值;set discountRate 做入参校验,保证业务不变式;get dynamicPrice 实现销量驱动的定价策略。
5.4 批量属性定义
使用Object.defineProperties进行批量操作:
// 【代码注释】ORM 风格示例:用访问器 + defineProperties 封装脏检查,模拟「只 UPDATE 变更列」
class DatabaseModel {
constructor(tableName, data = {}) {
this.tableName = tableName;
this._data = data;
this._modifiedFields = new Set(); // 脏字段集合,O(1) 增删
this._isNew = true;
Object.defineProperties(this, {
tableName: {
value: tableName,
writable: false,
enumerable: true,
configurable: false
},
modifiedFields: {
get() {
return Array.from(this._modifiedFields); // 对外暴露快照数组
},
enumerable: true,
configurable: true
},
isNew: {
get() { return this._isNew; },
enumerable: true,
configurable: true
},
data: {
get() {
return { ...this._data }; // 防止外部直接 mutate 内部 _data
},
set(value) {
const oldData = { ...this._data };
this._data = { ...value };
Object.keys(value).forEach(key => {
if (oldData[key] !== value[key]) {
this._modifiedFields.add(key);
}
});
},
enumerable: true,
configurable: true
}
});
}
save() {
const modifiedData = {};
this._modifiedFields.forEach(field => {
modifiedData[field] = this._data[field];
});
console.log(`保存到表 ${this.tableName}:`, modifiedData);
this._modifiedFields.clear();
this._isNew = false;
}
}
const userModel = new DatabaseModel('users', {
id: 1,
username: 'zhangsan',
email: 'zhangsan@example.com'
});
console.log('初始状态:', userModel.isNew); // true
userModel.data = {
...userModel.data,
email: 'newemail@example.com'
};
console.log('修改的字段:', userModel.modifiedFields); // ['email']
userModel.save();
【代码注释】 Object.defineProperties 批量配置:一次调用设置多个属性的描述符,适合 ORM / 模型层场景;modifiedFields getter 自动追踪脏字段,save() 只提交变更列。
5.5 对象密封与冻结
对象保护级别的完整对比:
【代码注释】 对象保护级别对比:普通对象 < preventExtensions(禁添)< seal(禁添禁删)< freeze(完全只读)。配置项用 freeze,运行时状态用 seal。
对象保护实战应用:
// 【代码注释】freeze vs seal:静态配置完全只读;运行时上下文允许改值但禁止增删键
class ConfigurationManager {
constructor() {
const defaultConfig = {
apiVersion: 'v2',
timeout: 30000,
retryAttempts: 3,
debugMode: false,
logLevel: 'info'
};
const productionConfig = {
timeout: 60000,
debugMode: false,
logLevel: 'error'
};
const developmentConfig = {
timeout: 10000,
debugMode: true,
logLevel: 'debug'
};
this.config = Object.freeze({
...defaultConfig,
...(this.detectEnvironment() === 'production' ? productionConfig : developmentConfig)
});
this.runtimeConfig = Object.seal({
currentUserId: null,
sessionId: null,
lastActivity: Date.now()
});
}
detectEnvironment() {
return process.env.NODE_ENV || 'development';
}
getConfig() {
return this.config; // 返回冻结对象引用,调用方不应尝试改写
}
updateRuntimeConfig(updates) {
Object.keys(updates).forEach(key => {
if (key in this.runtimeConfig) {
this.runtimeConfig[key] = updates[key];
}
// seal 下新增键如 updates.foo 会被忽略(非严格)或抛错(严格)
});
}
checkObjectStates() {
return {
config: {
frozen: Object.isFrozen(this.config),
sealed: Object.isSealed(this.config),
extensible: Object.isExtensible(this.config)
},
runtimeConfig: {
frozen: Object.isFrozen(this.runtimeConfig),
sealed: Object.isSealed(this.runtimeConfig),
extensible: Object.isExtensible(this.runtimeConfig)
}
};
}
}
// 【代码注释】使用配置管理器
const configManager = new ConfigurationManager();
// 尝试修改冻结的配置(严格模式下会报错)
try {
configManager.config.timeout = 99999; // 静默失败
} catch (error) {
console.error('无法修改冻结的配置');
}
// 验证配置未改变
console.log('配置超时值:', configManager.config.timeout); // 仍为原值
// 更新运行时配置(密封对象允许修改现有属性)
configManager.updateRuntimeConfig({
currentUserId: 12345,
sessionId: 'session_abc123'
});
console.log('运行时配置:', configManager.runtimeConfig);
console.log('对象状态检查:', configManager.checkObjectStates());
【代码注释】 生产级配置管理:Object.freeze 确保静态配置不可变;Object.seal 允许修改现有键值(如 sessionId)但阻止新增键;isFrozen/isSealed 用于运行时断言。
6. 深拷贝与浅拷贝深度解析
6.1 拷贝技术原理对比
名词解析:深拷贝(Deep Copy)
深拷贝会递归复制对象的所有层级,创建完全独立的副本,原始对象和拷贝对象之间不共享任何引用。
【代码注释】 拷贝策略选型:[...arr] / {...obj} 适合顶层扁平数据;深拷贝处理嵌套与循环引用;structuredClone 原生方案性能最优,2022 年起所有现代浏览器支持。
6.2 深拷贝完整实现
生产级深拷贝函数:
// 【代码注释】手写深拷贝要点:类型分支 + 循环引用表 + 原型链 + Symbol 键
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
function deepClone(obj, hash = new WeakMap()) {
// 原始类型与 null:按值返回;函数通常不克隆(structuredClone 亦不支持)
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
if (obj instanceof Map) {
const clone = new Map();
obj.forEach((value, key) => {
clone.set(deepClone(key, hash), deepClone(value, hash));
});
return clone;
}
if (obj instanceof Set) {
const clone = new Set();
obj.forEach(value => {
clone.add(deepClone(value, hash));
});
return clone;
}
if (obj instanceof ArrayBuffer) {
return obj.slice(0);
}
// WeakMap:键为对象时不阻止 GC;已访问过的对象直接返回已创建的克隆,打破环
if (hash.has(obj)) {
return hash.get(obj);
}
const Ctor = obj.constructor;
if (Ctor !== Object && Ctor !== Array) {
try {
return new Ctor(obj);
} catch (error) {
console.warn('无法克隆特殊对象类型:', Ctor.name);
return obj;
}
}
const clone = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
hash.set(obj, clone); // 必须在递归子属性之前登记,否则循环引用会栈溢出
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (const symKey of symbolKeys) {
clone[symKey] = deepClone(obj[symKey], hash);
}
return clone;
}
const complexObject = {
primitive: 123,
string: 'hello',
array: [1, 2, 3, { nested: 'value' }],
date: new Date(),
regex: /test/g,
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
nested: { level1: { level2: { value: 'deeply nested' } } },
func: function() { return 'original'; }
};
complexObject.circular = complexObject;
const clonedObject = deepClone(complexObject);
console.log('原始对象循环引用:', complexObject.circular === complexObject);
console.log('拷贝对象循环引用:', clonedObject.circular === clonedObject);
console.log('根对象独立:', complexObject !== clonedObject);
console.log('嵌套对象独立:', complexObject.nested !== clonedObject.nested);
【代码注释】 生产级深拷贝:WeakMap 记录已访问对象防循环引用;分支处理 Date/RegExp/Map/Set/ArrayBuffer;递归拷贝 for...in 自有属性与 Symbol 键;structuredClone 不能处理函数。
6.3 现代深拷贝方案对比
2024年推荐深拷贝方案:
// 【代码注释】现代深拷贝选型:优先 structuredClone,其次 JSON,最后手写/lodash
function modernDeepClone(obj) {
try {
return structuredClone(obj);
// 支持:Date/RegExp/Map/Set/ArrayBuffer/循环引用
// 不支持:函数/DOM 节点/部分内置对象 → 抛 DOMException
} catch (error) {
console.warn('structuredClone失败,使用备用方案:', error);
return fallbackDeepClone(obj);
}
}
function jsonDeepClone(obj) {
try {
return JSON.parse(JSON.stringify(obj));
// 丢失:undefined/Symbol/函数;Date 变字符串;不支持循环引用
} catch (error) {
console.error('JSON深拷贝失败:', error);
return null;
}
}
// 【代码注释】方案三:Lodash cloneDeep(需要引入库)
// import _ from 'lodash';
// const lodashClone = (obj) => _.cloneDeep(obj);
// 【代码注释】性能测试函数
function benchmarkDeepClones() {
const testObject = {
user: {
id: 1,
name: '张三',
profile: {
age: 25,
address: {
city: '北京',
district: '朝阳区'
}
}
},
orders: [
{ id: 'A001', items: ['商品1', '商品2'] },
{ id: 'A002', items: ['商品3', '商品4'] }
]
};
const methods = [
{
name: 'structuredClone',
fn: () => structuredClone(testObject)
},
{
name: 'JSON方法',
fn: () => JSON.parse(JSON.stringify(testObject))
},
{
name: '自定义deepClone',
fn: () => deepClone(testObject)
}
];
const results = {};
methods.forEach(method => {
const start = performance.now();
const iterations = 10000;
for (let i = 0; i < iterations; i++) {
method.fn();
}
const end = performance.now();
const avgTime = (end - start) / iterations;
results[method.name] = {
平均耗时: `${avgTime.toFixed(4)}ms`,
性能评分: avgTime < 0.01 ? '优秀' : avgTime < 0.1 ? '良好' : '一般'
};
});
return results;
}
// 【代码注释】运行性能测试
console.log('深拷贝性能测试:', benchmarkDeepClones());
【代码注释】 三种方案对比:structuredClone(原生,推荐)支持 Date/Map/Set/循环引用,但不支持函数;JSON 方法丢失 undefined/函数/Symbol;手写方案可定制但维护成本高。
6.4 实战应用:状态管理系统
// 【代码注释】迷你 Redux:内部单源状态 + 订阅发布 + 深拷贝隔离 + 历史栈 undo
class StateManager {
constructor(initialState = {}) {
this._state = deepClone(initialState);
this._listeners = [];
this._history = [];
this._maxHistorySize = 50;
}
getState() {
return deepClone(this._state); // 外部拿到的副本无法污染内部 _state
}
setState(updater) {
const oldState = this._state;
let newState;
if (typeof updater === 'function') {
newState = updater(deepClone(oldState)); // 函数式更新,类似 React setState
} else {
newState = deepClone(updater);
}
this._history.push(deepClone(oldState));
if (this._history.length > this._maxHistorySize) {
this._history.shift(); // 限制栈深,防止内存无限增长
}
this._state = newState;
this.notifyListeners();
}
undo() {
if (this._history.length > 0) {
this._state = this._history.pop();
this.notifyListeners();
}
}
subscribe(listener) {
this._listeners.push(listener);
return () => {
const index = this._listeners.indexOf(listener);
if (index > -1) {
this._listeners.splice(index, 1);
}
};
}
notifyListeners() {
const stateCopy = this.getState();
this._listeners.forEach(listener => listener(stateCopy));
}
}
const cartState = new StateManager({
items: [],
total: 0,
discount: 0
});
cartState.subscribe((state) => {
console.log('购物车状态更新:', state);
});
cartState.setState((prevState) => ({
...prevState,
items: [...prevState.items, { id: 1, name: '商品1', price: 100, quantity: 1 }]
}));
cartState.setState((prevState) => ({
...prevState,
items: prevState.items.map(item =>
item.id === 1 ? { ...item, quantity: item.quantity + 1 } : item
)
}));
【代码注释】 简版状态管理器:deepClone 确保取出的状态不被外部修改;setState 先记录旧状态到历史栈再更新;undo 弹出栈顶回滚;subscribe 返回取消订阅函数,防内存泄漏。
7. 实战应用场景
7.1 电商平台数据处理
// 【代码注释】Pipeline:每个 processor 是纯函数 data => data,reduce 从左到右串联
class ECommerceProcessor {
constructor() {
this.processors = [];
}
use(processor) {
this.processors.push(processor);
return this; // 链式调用 .use().use()
}
process(data) {
return this.processors.reduce((result, processor) => {
return processor(result);
}, data);
}
}
const normalizeData = (data) => {
return data.map(item => ({
id: item.product_id || item.id,
name: item.product_name || item.name,
price: parseFloat(item.price) || 0,
category: item.category || '未分类',
stock: parseInt(item.stock) || 0,
images: Array.isArray(item.images) ? item.images : []
}));
};
// 【代码注释】价格计算处理器
const calculatePrices = (data) => {
return data.map(item => {
const discount = item.discount || 0;
return {
...item,
originalPrice: item.price,
finalPrice: item.price * (1 - discount / 100),
savings: item.price * (discount / 100)
};
});
};
// 【代码注释】库存检查处理器
const checkStock = (data) => {
return data.filter(item => item.stock > 0).map(item => ({
...item,
inStock: true,
stockStatus: item.stock > 10 ? '充足' : '紧张'
}));
};
// 【代码注释】分类整理处理器
const categorizeProducts = (data) => {
const categories = {};
data.forEach(item => {
if (!categories[item.category]) {
categories[item.category] = [];
}
categories[item.category].push(item);
});
return {
allProducts: data,
categories,
categoryCount: Object.keys(categories).length,
totalProducts: data.length
};
};
// 【代码注释】使用处理管道
const rawData = [
{ product_id: '1', product_name: '手机', price: '2999', category: '电子产品', stock: '50', discount: 10 },
{ product_id: '2', product_name: '笔记本', price: '5999', category: '电子产品', stock: '5', discount: 15 },
{ id: '3', name: 'T恤', price: '99', category: '服装', stock: '0', discount: 0 }
];
const processor = new ECommerceProcessor()
.use(normalizeData)
.use(calculatePrices)
.use(checkStock)
.use(categorizeProducts);
const processedData = processor.process(rawData);
console.log('处理后的电商数据:', processedData);
【代码注释】 数据处理管道(Pipeline 模式):每个处理器只负责单一转换(标准化→价格计算→库存过滤→分类),链式 use() 注册、reduce 驱动,符合开闭原则,易于单测。
7.2 用户权限管理系统
// 【代码注释】RBAC:角色 → 权限列表;用户 → 权限 Set;鉴权 O(1) 的 has 查询
class PermissionManager {
constructor() {
this.roles = new Map([
['admin', ['create', 'read', 'update', 'delete', 'manage_users']],
['editor', ['create', 'read', 'update']],
['viewer', ['read']]
]);
this.userPermissions = new Map(); // userId -> Set<permission>
}
assignRole(userId, role) {
if (!this.roles.has(role)) {
throw new Error(`未知角色:${role}`);
}
const permissions = this.roles.get(role);
this.userPermissions.set(userId, new Set(permissions));
return this;
}
hasPermission(userId, permission) {
const userPermissions = this.userPermissions.get(userId);
return userPermissions ? userPermissions.has(permission) : false;
}
addPermission(userId, permission) {
if (!this.userPermissions.has(userId)) {
this.userPermissions.set(userId, new Set());
}
this.userPermissions.get(userId).add(permission);
return this;
}
removePermission(userId, permission) {
const permissions = this.userPermissions.get(userId);
if (permissions) {
permissions.delete(permission);
}
return this;
}
getUserPermissions(userId) {
const permissions = this.userPermissions.get(userId);
return permissions ? Array.from(permissions) : [];
}
}
const authManager = new PermissionManager();
// 分配角色
authManager.assignRole('user123', 'editor');
authManager.addPermission('user123', 'publish'); // 额外授予发布权限
// 检查权限
console.log('user123可以编辑:', authManager.hasPermission('user123', 'update')); // true
console.log('user123可以删除:', authManager.hasPermission('user123', 'delete')); // false
console.log('user123可以发布:', authManager.hasPermission('user123', 'publish')); // true
// 获取所有权限
console.log('user123的权限列表:', authManager.getUserPermissions('user123'));
【代码注释】 RBAC 权限管理:Map 存储角色→权限映射;Set 存储用户权限集合,has O(1) 查找;assignRole + addPermission 支持细粒度自定义;Array.from 将 Set 转数组供序列化。
8. 性能优化建议
8.1 内存管理最佳实践
// 【代码注释】WeakMap 做对象元数据:不增加强引用计数,对象被 GC 后条目自动消失
class MemoryMonitor {
constructor() {
this.trackedObjects = new WeakMap();
this.memoryUsage = [];
}
track(object, label) {
this.trackedObjects.set(object, {
label,
created: Date.now(),
size: this.estimateSize(object)
});
}
estimateSize(obj) {
let size = 0;
if (typeof obj === 'string') {
size = obj.length * 2; // UTF-16编码
} else if (typeof obj === 'number') {
size = 8;
} else if (typeof obj === 'boolean') {
size = 4;
} else if (obj instanceof Array) {
size = obj.length * 8; // 粗略估算
obj.forEach(item => {
size += this.estimateSize(item);
});
} else if (typeof obj === 'object' && obj !== null) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
size += key.length * 2;
size += this.estimateSize(obj[key]);
}
}
}
return size;
}
// 【代码注释】生成内存报告
generateReport() {
const report = {
timestamp: Date.now(),
trackedObjects: this.trackedObjects.size,
totalSize: 0,
objects: []
};
// 注意:WeakMap不可遍历,这里只是示例结构
console.log('内存报告生成中...');
return report;
}
}
// 【代码注释】使用内存监控
const memoryMonitor = new MemoryMonitor();
const largeDataSet = {
users: Array(1000).fill(0).map((_, i) => ({
id: i,
name: `用户${i}`,
data: new Array(100).fill('测试数据')
}))
};
memoryMonitor.track(largeDataSet, '大数据集');
【代码注释】 WeakMap 追踪对象而不阻止 GC:当被追踪对象被回收时,条目自动消失,无需手动清理;estimateSize 按类型递归估算内存占用,适合开发阶段性能分析工具。
8.2 数据处理优化技巧
// 【代码注释】大批量任务:分片处理 + 宏任务让出主线程,避免长任务阻塞渲染(>50ms)
class BatchProcessor {
constructor(batchSize = 100) {
this.batchSize = batchSize;
this.queue = [];
this.processing = false;
}
add(item) {
this.queue.push(item);
if (!this.processing) {
this.processQueue();
}
}
async processQueue() {
this.processing = true;
while (this.queue.length > 0) {
const batch = this.queue.splice(0, this.batchSize);
await this.processBatch(batch);
await new Promise(resolve => setTimeout(resolve, 0));
}
this.processing = false;
}
async processBatch(batch) {
console.log(`处理批次:${batch.length}项`);
// 模拟异步操作
return new Promise(resolve => {
setTimeout(() => {
resolve(batch.map(item => ({ ...item, processed: true })));
}, 100);
});
}
}
// 【代码注释】使用批量处理器
const processor = new BatchProcessor(50);
// 添加大量数据
for (let i = 0; i < 1000; i++) {
processor.add({ id: i, data: `项目${i}` });
}
【代码注释】 批量处理器的关键技巧:setTimeout(resolve, 0) 让出主线程,防止大批量任务阻塞 UI 渲染;splice(0, batchSize) 每次取固定量处理,控制内存峰值。
9. 最佳实践总结
9.1 代码设计原则
SOLID原则在JavaScript中的应用:
// 【代码注释】SOLID 在 JS 中的落地:拆分类职责、策略对象扩展、构造函数注入依赖
class UserRepository {
constructor(db) {
this.db = db;
}
async findById(id) {
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
async create(userData) {
return this.db.query('INSERT INTO users SET ?', userData);
}
}
class EmailService {
constructor(smtpConfig) {
this.smtp = smtpConfig;
}
async sendWelcomeEmail(userEmail, userName) {
// 发送欢迎邮件逻辑
}
}
// 【代码注释】开闭原则(OCP)
class PaymentProcessor {
async processPayment(paymentMethod, amount) {
const processor = this.getProcessor(paymentMethod);
return processor.process(amount);
}
getProcessor(method) {
const processors = {
'credit_card': new CreditCardProcessor(),
'paypal': new PayPalProcessor(),
'wechat': new WeChatProcessor()
};
return processors[method] || new DefaultProcessor();
}
}
// 【代码注释】依赖倒置原则(DIP)
class OrderService {
constructor(paymentProcessor, notificationService) {
this.paymentProcessor = paymentProcessor;
this.notificationService = notificationService;
}
async createOrder(orderData) {
// 处理订单逻辑
await this.paymentProcessor.processPayment(orderData.payment);
await this.notificationService.sendConfirmation(orderData.userEmail);
}
}
【代码注释】 SOLID 三原则示范:UserRepository / EmailService 各司其职(SRP);PaymentProcessor.getProcessor 对扩展开放、对修改封闭(OCP);OrderService 依赖抽象接口(DIP)。
9.2 错误处理模式
// 【代码注释】Result(铁路导向):用返回值承载失败,避免 try/catch 打断业务流
class Result {
constructor(success, data, error) {
this.success = success;
this.data = data;
this.error = error;
}
static ok(data) {
return new Result(true, data, null);
}
static error(error) {
return new Result(false, null, error);
}
map(fn) {
return this.success ?
Result.ok(fn(this.data)) :
Result.error(this.error);
}
flatMap(fn) {
return this.success ?
fn(this.data) : // fn 须返回 Result,用于可能失败的下一步
Result.error(this.error);
}
unwrap() {
if (this.success) {
return this.data;
} else {
throw new Error(`Operation failed: ${this.error}`);
}
}
}
function validateUser(userData) {
if (!userData.email || !userData.email.includes('@')) {
return Result.error('无效的邮箱地址');
}
if (!userData.password || userData.password.length < 6) {
return Result.error('密码长度不能少于6位');
}
return Result.ok(userData);
}
function createUser(userData) {
return validateUser(userData)
.flatMap(validated => saveUserToDatabase(validated))
.map(user => sendWelcomeEmail(user.email));
}
// 【代码注释】调用示例
const result = createUser({
email: 'user@example.com',
password: 'secure123'
});
if (result.success) {
console.log('用户创建成功:', result.data);
} else {
console.error('用户创建失败:', result.error);
}
【代码注释】 Result 模式(Railway-Oriented Programming):用 Result.ok/Result.error 代替抛异常,链式 map/flatMap 传递成功值,失败时短路,调用方显式处理两条路径。
9.3 异步编程最佳实践
// 【代码注释】Promise 工具集:容错聚合、限流批处理、线性退避重试
class PromiseUtils {
static async allSafe(promises) {
const results = await Promise.allSettled(promises);
const successes = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
const failures = results
.filter(r => r.status === 'rejected')
.map(r => r.reason);
return {
successes,
failures,
allSuccessful: failures.length === 0
};
}
static async batch(items, processor, batchSize = 10) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(item => processor(item))
);
results.push(...batchResults);
}
return results;
}
static async retry(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`重试第${i + 1}次...`);
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
}
}
}
}
async function processMultipleAPIs() {
const apiCalls = [
fetch('https://api.example.com/users'),
fetch('https://api.example.com/products'),
fetch('https://api.example.com/orders')
];
const result = await PromiseUtils.allSafe(apiCalls);
if (result.allSuccessful) {
console.log('所有API调用成功');
return result.successes;
} else {
console.warn('部分API调用失败:', result.failures);
return result.successes;
}
}
【代码注释】 PromiseUtils 三把利器:allSafe 用 allSettled 容错(不因单个失败中断);batch 控制并发数防止请求风暴;retry 指数退避重试,生产级 API 客户端必备。
10. Day04 知识点速查
| 模块 | 核心要点 | 框架关联 |
|---|---|---|
| 数据类型 | 7 原始 + 多对象类型;原始值传递、引用共享 | 所有框架基础 |
| 变量声明 | let/const 不提升、不重复声明、块级作用域 TDZ |
ESLint prefer-const |
| 箭头函数 | 词法 this;不可 new;无 arguments |
React/Vue 回调、Promise 链 |
| 模板字符串 | 多行、插值、标签模板(Tagged Template) | styled-components、gql |
| 解构/展开 | 按位置/名称提取;...rest 收集;对象合并 |
Redux 状态更新、React props |
| ES6 模块 | 静态分析、Tree Shaking、Live Binding | Vite/Webpack 打包优化 |
| Class 类 | 私有字段 #、静态、继承、Mixin |
React 类组件、Vue Options API |
| Iterator/Generator | 惰性求值、自定义迭代、状态机、异步流 | 分页游标、for await...of |
| Proxy/Reflect | 透明拦截、元编程、响应式系统 | Vue 3 reactive、数据验证 |
| 可选链/空值合并 | ?. 安全访问、?? 区分合法假值 |
API 响应解析、默认参数 |
| 数组 | flat/flatMap 扁平化;slice/[...] 浅拷贝 |
数据管道处理 |
| 对象描述符 | defineProperty;get/set 访问器 |
Vue 2 响应式 |
| 深拷贝 | structuredClone > JSON > 手写;WeakMap 防环 |
状态管理、Undo/Redo |
【代码注释】 ES6 完整总结思维导图:从类型体系出发,经过声明/函数/类/迭代/元编程/模块,最终落地数据处理。每个节点都有与之对应的主流框架(Vue/React/Node.js)的实际应用——这张地图可作为面试复盘和技术选型的速查参考。
总结
本指南涵盖了ES6从基础到高级的完整知识体系与实战应用技巧:
核心知识点回顾:
- 数据类型系统:7种原始类型和多种对象类型的深入理解,BigInt/Symbol 使用场景
- 变量声明:let/const vs var,块级作用域、TDZ 的重要性,ESLint 最佳实践
- 函数增强:箭头函数词法 this、模板字符串与标签模板、默认参数、Rest/Spread
- 解构与展开:数组/对象解构、不可变更新模式、函数参数自文档化
- ES6 模块:ESM 静态分析、Tree Shaking、Live Binding 与 CJS 的本质差异
- Class 类系统:私有字段(
#)、静态成员、继承链、Mixin 多继承模拟 - Iterator / Generator:可迭代协议、惰性求值、分页游标、状态机、异步迭代
- Proxy / Reflect:透明拦截、响应式系统原理(Vue 3 核心)、数据验证、AOP
- 可选链与空值合并:
?.安全访问、??区分合法假值、逻辑赋值运算符 - 数组操作:扁平化、拷贝技术的多种实现和性能对比
- 对象特性:属性描述符、访问器属性的实战应用(Vue 2 响应式基础)
- 深拷贝技术:从基础到生产级的完整解决方案,structuredClone 现代推荐
- 实战应用:电商数据管道、RBAC 权限管理、状态管理系统等真实业务场景
学习路径建议:
- 初学者:重点掌握数据类型、let/const 块级作用域、箭头函数、模板字符串、基础解构
- 中级开发者:深入理解 Class 继承、Iterator/Generator、对象描述符、浅/深拷贝、异步编程
- 高级开发者:关注 Proxy/Reflect 元编程、ESM Tree Shaking、性能优化、架构设计(SOLID/Pipeline/RBAC)
- 框架学习者:用 Proxy 理解 Vue 3 响应式;用展开运算符理解 Redux 不可变更新;用 Generator 理解 async/await 原理
持续学习资源:
- ECMAScript官方规范:ECMA-262
- MDN Web Docs:JavaScript Guide
- 现代JavaScript教程:javascript.info
参考资料:
- MDN - Object.getOwnPropertyDescriptor()
- javascript.info - Property Descriptors
- MDN - Deep Copy
- Six Types of Scope in JavaScript
- Function Scope & Block Scope in JS
附录:官方与延伸阅读
| 资源 | 链接 | 说明 |
|---|---|---|
| MDN · 数据类型 | https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures | 原始类型与对象 |
| MDN · defineProperty | https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty | 属性描述符 |
| MDN · 深拷贝 | https://developer.mozilla.org/zh-CN/docs/Glossary/Deep_copy | 拷贝概念 |
| javascript.info | https://zh.javascript.info/object-properties | 对象属性详解 |
文中 HTML 示例可直接保存后在浏览器打开验证;对象/数组练习建议在控制台逐步执行。
更多推荐


所有评论(0)