JavaScript|第9章:对象 --- 基础
对象基础
- [JavaScript|第9章:对象 --- 基础](#JavaScript|第9章:对象 — 基础)
-
- 引言
- [9.1 对象的创建方式](#9.1 对象的创建方式)
-
- [9.1.1 对象字面量(Object Literal)](#9.1.1 对象字面量(Object Literal))
- [9.1.2 new Object() 构造函数](#9.1.2 new Object() 构造函数)
- [9.1.3 两种方式的对比](#9.1.3 两种方式的对比)
- [9.2 属性的增删改查](#9.2 属性的增删改查)
-
- [9.2.1 读取属性](#9.2.1 读取属性)
- [9.2.2 添加与修改属性](#9.2.2 添加与修改属性)
- [9.2.3 删除属性:delete 操作符](#9.2.3 删除属性:delete 操作符)
- [9.2.4 检查属性是否存在:in 操作符](#9.2.4 检查属性是否存在:in 操作符)
- [9.2.5 遍历对象属性](#9.2.5 遍历对象属性)
- [9.3 对象引用与复制](#9.3 对象引用与复制)
-
- [9.3.1 原始类型 vs 引用类型](#9.3.1 原始类型 vs 引用类型)
- [9.3.2 比较:引用相等](#9.3.2 比较:引用相等)
- [9.3.3 浅拷贝(Shallow Copy)](#9.3.3 浅拷贝(Shallow Copy))
- [9.3.4 深拷贝(Deep Copy)](#9.3.4 深拷贝(Deep Copy))
- [9.3.5 structuredClone(现代方案)](#9.3.5 structuredClone(现代方案))
- [9.4 垃圾回收机制基础](#9.4 垃圾回收机制基础)
-
- [9.4.1 可达性(Reachability)](#9.4.1 可达性(Reachability))
- [9.4.2 垃圾回收算法:标记-清除](#9.4.2 垃圾回收算法:标记-清除)
- [9.4.3 常见内存泄漏场景](#9.4.3 常见内存泄漏场景)
- [9.5 Object 静态方法](#9.5 Object 静态方法)
-
- [9.5.1 Object.keys / values / entries](#9.5.1 Object.keys / values / entries)
- [9.5.2 Object.assign:对象合并](#9.5.2 Object.assign:对象合并)
- [9.5.3 Object.freeze / seal / preventExtensions](#9.5.3 Object.freeze / seal / preventExtensions)
- [9.5.4 其他常用 Object 方法](#9.5.4 其他常用 Object 方法)
- [9.6 实战:对象合并与深拷贝实现](#9.6 实战:对象合并与深拷贝实现)
-
- [9.6.1 实战1:智能对象合并](#9.6.1 实战1:智能对象合并)
- [9.6.2 实战2:手写深拷贝](#9.6.2 实战2:手写深拷贝)
- [9.6.3 深拷贝实现要点解析](#9.6.3 深拷贝实现要点解析)
- 本章小结
上篇回顾 :第8章:代码质量
下篇预告 :第10章:对象方法与 this
引言
在前面的章节中,我们学习了原始类型(number、string、boolean 等)。原始类型只能存储单个值,但现实世界的数据往往是结构化的------一个用户有姓名、年龄、邮箱、地址......用一堆独立的变量来管理这些数据,既不直观,也难以维护。
对象(Object)是 JavaScript 中最核心的数据结构。它将相关的数据和行为组织在一起,是构建复杂程序的基石。事实上,JavaScript 中几乎所有东西都是对象------数组是对象、函数是对象、甚至日期也是对象。
#mermaid-svg-3YEU9aPzmx8yLdn6{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-3YEU9aPzmx8yLdn6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3YEU9aPzmx8yLdn6 .error-icon{fill:#552222;}#mermaid-svg-3YEU9aPzmx8yLdn6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3YEU9aPzmx8yLdn6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .marker.cross{stroke:#333333;}#mermaid-svg-3YEU9aPzmx8yLdn6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3YEU9aPzmx8yLdn6 p{margin:0;}#mermaid-svg-3YEU9aPzmx8yLdn6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .cluster-label text{fill:#333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .cluster-label span{color:#333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .cluster-label span p{background-color:transparent;}#mermaid-svg-3YEU9aPzmx8yLdn6 .label text,#mermaid-svg-3YEU9aPzmx8yLdn6 span{fill:#333;color:#333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .node rect,#mermaid-svg-3YEU9aPzmx8yLdn6 .node circle,#mermaid-svg-3YEU9aPzmx8yLdn6 .node ellipse,#mermaid-svg-3YEU9aPzmx8yLdn6 .node polygon,#mermaid-svg-3YEU9aPzmx8yLdn6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3YEU9aPzmx8yLdn6 .rough-node .label text,#mermaid-svg-3YEU9aPzmx8yLdn6 .node .label text,#mermaid-svg-3YEU9aPzmx8yLdn6 .image-shape .label,#mermaid-svg-3YEU9aPzmx8yLdn6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-3YEU9aPzmx8yLdn6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3YEU9aPzmx8yLdn6 .rough-node .label,#mermaid-svg-3YEU9aPzmx8yLdn6 .node .label,#mermaid-svg-3YEU9aPzmx8yLdn6 .image-shape .label,#mermaid-svg-3YEU9aPzmx8yLdn6 .icon-shape .label{text-align:center;}#mermaid-svg-3YEU9aPzmx8yLdn6 .node.clickable{cursor:pointer;}#mermaid-svg-3YEU9aPzmx8yLdn6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .arrowheadPath{fill:#333333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3YEU9aPzmx8yLdn6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3YEU9aPzmx8yLdn6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3YEU9aPzmx8yLdn6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3YEU9aPzmx8yLdn6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3YEU9aPzmx8yLdn6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3YEU9aPzmx8yLdn6 .cluster text{fill:#333;}#mermaid-svg-3YEU9aPzmx8yLdn6 .cluster span{color:#333;}#mermaid-svg-3YEU9aPzmx8yLdn6 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-3YEU9aPzmx8yLdn6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3YEU9aPzmx8yLdn6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-3YEU9aPzmx8yLdn6 .icon-shape,#mermaid-svg-3YEU9aPzmx8yLdn6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3YEU9aPzmx8yLdn6 .icon-shape p,#mermaid-svg-3YEU9aPzmx8yLdn6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3YEU9aPzmx8yLdn6 .icon-shape .label rect,#mermaid-svg-3YEU9aPzmx8yLdn6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3YEU9aPzmx8yLdn6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3YEU9aPzmx8yLdn6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3YEU9aPzmx8yLdn6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} JavaScript 数据类型
原始类型
引用类型(对象)
number / string / boolean
null / undefined
symbol / bigint
普通对象 {}
数组 \[\]
函数 function
Date / RegExp / Map / Set
本章目标:
- 掌握对象的两种创建方式:字面量与构造函数
- 熟练进行属性的增删改查,理解
in与delete操作符 - 理解对象的引用本质,区分浅拷贝与深拷贝
- 了解 JavaScript 的垃圾回收机制
- 掌握
Object常用静态方法 - 实战:实现一个支持嵌套对象的深拷贝函数
9.1 对象的创建方式
JavaScript 提供了多种创建对象的方式,最常用的是对象字面量 和 new Object() 构造函数。
9.1.1 对象字面量(Object Literal)
对象字面量是最直观、最推荐的创建方式,用花括号 {} 包裹键值对。
javascript
// 对象字面量
const user = {
name: 'Alice',
age: 25,
email: 'alice@example.com',
isActive: true
};
console.log(user.name); // "Alice"
console.log(user.age); // 25
属性名(Key)的规则:
javascript
const obj = {
name: 'Alice', // 合法的标识符 → 不需要引号
'first name': '张', // 包含空格 → 必须加引号
'2024-year': true, // 以数字开头 → 必须加引号
'': '空字符串键', // 空字符串也可以作为键
[Symbol()]: 'Symbol键' // Symbol 作为键
};
计算属性名(ES6):
javascript
const field = 'email';
const user = {
name: 'Alice',
[field]: 'alice@example.com' // 动态计算属性名
};
console.log(user.email); // "alice@example.com"
// 更复杂的计算
const prefix = 'user';
const config = {
[`${prefix}Name`]: 'Alice', // → userName
[`${prefix}Age`]: 25 // → userAge
};
属性简写(ES6):当变量名和属性名相同时,可以省略冒号和值。
javascript
const name = 'Alice';
const age = 25;
// 简写(推荐)
const user = { name, age };
// 等价于
const user = { name: name, age: age };
9.1.2 new Object() 构造函数
javascript
const user = new Object();
user.name = 'Alice';
user.age = 25;
console.log(user); // { name: 'Alice', age: 25 }
不推荐 :
new Object()写法冗长,且没有字面量直观。在实际开发中几乎总是使用字面量。
9.1.3 两种方式的对比
| 特性 | 对象字面量 {} |
new Object() |
|---|---|---|
| 语法简洁度 | ✅ 简洁直观 | ❌ 冗长 |
| 性能 | ✅ 引擎优化更好 | 略慢(需要调用构造函数) |
| 可读性 | ✅ 一眼看出结构 | ❌ 需要逐行阅读 |
| 动态属性 | 支持计算属性名 | 需要赋值语句 |
| 推荐程度 | ✅ 强烈推荐 | ❌ 不推荐 |
javascript
// ✅ 推荐:字面量
const product = {
name: 'MacBook Pro',
price: 14999,
specs: { cpu: 'M3', ram: '16GB' }
};
// ❌ 不推荐:new Object()
const product = new Object();
product.name = 'MacBook Pro';
product.price = 14999;
product.specs = new Object();
product.specs.cpu = 'M3';
product.specs.ram = '16GB';
9.2 属性的增删改查
对象创建之后,我们经常需要读取、添加、修改和删除属性。
9.2.1 读取属性
JavaScript 提供了两种读取属性的方式:点号(.) 和 方括号([])。
javascript
const user = {
name: 'Alice',
age: 25,
'full address': '北京市朝阳区'
};
// 点号访问(推荐,简洁)
console.log(user.name); // "Alice"
// 方括号访问(动态键或含特殊字符的键)
console.log(user['name']); // "Alice"
console.log(user['full address']); // "北京市朝阳区"
// 方括号支持动态计算
const field = 'age';
console.log(user[field]); // 25
// ❌ 点号不支持动态计算
// console.log(user.field); // undefined(查找名为 "field" 的属性)
访问不存在的属性返回 undefined:
javascript
const user = { name: 'Alice' };
console.log(user.age); // undefined(不是报错)
console.log(user.email); // undefined
注意 :访问不存在的属性不会报错,而是静默返回
undefined。这是 JavaScript 对象的一个重要特点,也是很多 Bug 的来源。
9.2.2 添加与修改属性
javascript
const user = { name: 'Alice' };
// 添加新属性
user.age = 25;
user.email = 'alice@example.com';
// 修改已有属性
user.name = 'Alice Wang';
console.log(user);
// { name: 'Alice Wang', age: 25, email: 'alice@example.com' }
批量添加属性 (使用展开运算符或 Object.assign):
javascript
const user = { name: 'Alice' };
const updates = { age: 25, email: 'alice@example.com' };
// 方式1:Object.assign
Object.assign(user, updates);
// 方式2:展开运算符(创建新对象)
const newUser = { ...user, ...updates };
9.2.3 删除属性:delete 操作符
javascript
const user = {
name: 'Alice',
age: 25,
email: 'alice@example.com'
};
delete user.email;
console.log(user); // { name: 'Alice', age: 25 }
console.log(user.email); // undefined
注意 :
delete只能删除对象自身的属性 ,不能删除变量或函数声明。delete操作成功返回true,失败返回false(如删除不可配置的属性)。
javascript
// delete 的返回值
const obj = { a: 1 };
console.log(delete obj.a); // true(删除成功)
console.log(delete obj.a); // true(属性不存在也算成功)
console.log(delete obj.unknown); // true
9.2.4 检查属性是否存在:in 操作符
由于访问不存在的属性返回 undefined,我们不能用 === undefined 来判断属性是否存在(属性可能存在但值就是 undefined)。in 操作符是更可靠的方式。
javascript
const user = { name: 'Alice', age: 25, hobby: undefined };
// ✅ in 操作符:检查属性是否存在(包括原型链上的属性)
console.log('name' in user); // true
console.log('age' in user); // true
console.log('hobby' in user); // true(属性存在,值为 undefined)
console.log('email' in user); // false(属性不存在)
// ❌ 不推荐:用 undefined 判断
console.log(user.hobby !== undefined); // false(但 hobby 属性确实存在)
console.log(user.email !== undefined); // false(email 属性确实不存在)
hasOwnProperty:只检查自身属性(不含原型链):
javascript
const user = { name: 'Alice' };
console.log('name' in user); // true
console.log('toString' in user); // true(来自 Object.prototype)
console.log(user.hasOwnProperty('name')); // true
console.log(user.hasOwnProperty('toString')); // false(不是自身属性)
// ES2022 推荐写法:Object.hasOwn
console.log(Object.hasOwn(user, 'name')); // true
console.log(Object.hasOwn(user, 'toString')); // false
| 方法 | 检查自身属性 | 检查原型链属性 | 推荐度 |
|---|---|---|---|
in |
✅ | ✅ | 需要了解原型链时使用 |
hasOwnProperty |
✅ | ❌ | 旧代码常见 |
Object.hasOwn |
✅ | ❌ | ✅ 现代推荐写法 |
9.2.5 遍历对象属性
javascript
const user = { name: 'Alice', age: 25, city: '北京' };
// for...in 遍历所有可枚举属性(包括原型链)
for (const key in user) {
if (Object.hasOwn(user, key)) { // 过滤原型链属性
console.log(`${key}: ${user[key]}`);
}
}
// Object.keys() 只获取自身可枚举属性名
console.log(Object.keys(user)); // ['name', 'age', 'city']
// Object.values() 只获取属性值
console.log(Object.values(user)); // ['Alice', 25, '北京']
// Object.entries() 获取键值对数组
for (const [key, value] of Object.entries(user)) {
console.log(`${key} = ${value}`);
}
属性顺序 :对象的属性遍历顺序有规则------整数键 按升序排列,字符串键 和 Symbol 键按插入顺序排列。
javascript
const obj = {
b: 2,
a: 1,
3: 'three',
1: 'one',
c: 3
};
console.log(Object.keys(obj));
// ['1', '3', 'b', 'a', 'c']
// 整数键 1, 3 按升序,字符串键 b, a, c 按插入顺序
9.3 对象引用与复制
理解对象的引用本质是避免 Bug 的关键。原始类型和引用类型在赋值时的行为完全不同。
9.3.1 原始类型 vs 引用类型
javascript
// 原始类型:赋值时复制值
let a = 10;
let b = a;
b = 20;
console.log(a); // 10(a 不受影响)
console.log(b); // 20
// 引用类型:赋值时复制引用(指向同一对象)
const user1 = { name: 'Alice', age: 25 };
const user2 = user1; // user2 和 user1 指向同一个对象
user2.name = 'Bob';
console.log(user1.name); // "Bob"(user1 也被修改了!)
console.log(user2.name); // "Bob"
#mermaid-svg-0QMK5Fhwy5XJLNap{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-0QMK5Fhwy5XJLNap .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0QMK5Fhwy5XJLNap .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0QMK5Fhwy5XJLNap .error-icon{fill:#552222;}#mermaid-svg-0QMK5Fhwy5XJLNap .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0QMK5Fhwy5XJLNap .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0QMK5Fhwy5XJLNap .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0QMK5Fhwy5XJLNap .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0QMK5Fhwy5XJLNap .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0QMK5Fhwy5XJLNap .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0QMK5Fhwy5XJLNap .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0QMK5Fhwy5XJLNap .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0QMK5Fhwy5XJLNap .marker.cross{stroke:#333333;}#mermaid-svg-0QMK5Fhwy5XJLNap svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0QMK5Fhwy5XJLNap p{margin:0;}#mermaid-svg-0QMK5Fhwy5XJLNap .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0QMK5Fhwy5XJLNap .cluster-label text{fill:#333;}#mermaid-svg-0QMK5Fhwy5XJLNap .cluster-label span{color:#333;}#mermaid-svg-0QMK5Fhwy5XJLNap .cluster-label span p{background-color:transparent;}#mermaid-svg-0QMK5Fhwy5XJLNap .label text,#mermaid-svg-0QMK5Fhwy5XJLNap span{fill:#333;color:#333;}#mermaid-svg-0QMK5Fhwy5XJLNap .node rect,#mermaid-svg-0QMK5Fhwy5XJLNap .node circle,#mermaid-svg-0QMK5Fhwy5XJLNap .node ellipse,#mermaid-svg-0QMK5Fhwy5XJLNap .node polygon,#mermaid-svg-0QMK5Fhwy5XJLNap .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0QMK5Fhwy5XJLNap .rough-node .label text,#mermaid-svg-0QMK5Fhwy5XJLNap .node .label text,#mermaid-svg-0QMK5Fhwy5XJLNap .image-shape .label,#mermaid-svg-0QMK5Fhwy5XJLNap .icon-shape .label{text-anchor:middle;}#mermaid-svg-0QMK5Fhwy5XJLNap .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0QMK5Fhwy5XJLNap .rough-node .label,#mermaid-svg-0QMK5Fhwy5XJLNap .node .label,#mermaid-svg-0QMK5Fhwy5XJLNap .image-shape .label,#mermaid-svg-0QMK5Fhwy5XJLNap .icon-shape .label{text-align:center;}#mermaid-svg-0QMK5Fhwy5XJLNap .node.clickable{cursor:pointer;}#mermaid-svg-0QMK5Fhwy5XJLNap .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0QMK5Fhwy5XJLNap .arrowheadPath{fill:#333333;}#mermaid-svg-0QMK5Fhwy5XJLNap .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0QMK5Fhwy5XJLNap .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0QMK5Fhwy5XJLNap .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0QMK5Fhwy5XJLNap .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0QMK5Fhwy5XJLNap .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0QMK5Fhwy5XJLNap .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0QMK5Fhwy5XJLNap .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0QMK5Fhwy5XJLNap .cluster text{fill:#333;}#mermaid-svg-0QMK5Fhwy5XJLNap .cluster span{color:#333;}#mermaid-svg-0QMK5Fhwy5XJLNap 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-0QMK5Fhwy5XJLNap .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0QMK5Fhwy5XJLNap rect.text{fill:none;stroke-width:0;}#mermaid-svg-0QMK5Fhwy5XJLNap .icon-shape,#mermaid-svg-0QMK5Fhwy5XJLNap .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0QMK5Fhwy5XJLNap .icon-shape p,#mermaid-svg-0QMK5Fhwy5XJLNap .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0QMK5Fhwy5XJLNap .icon-shape .label rect,#mermaid-svg-0QMK5Fhwy5XJLNap .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0QMK5Fhwy5XJLNap .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0QMK5Fhwy5XJLNap .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0QMK5Fhwy5XJLNap :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 指向
指向
user1
内存中的对象
name: 'Alice', age: 25
user2
关键理解 :对象变量存储的不是对象本身,而是对象在内存中的地址(引用)。赋值操作只是复制了地址,两个变量仍然指向同一块内存。
9.3.2 比较:引用相等
javascript
// 原始类型:值相等即为 true
console.log(1 === 1); // true
// 引用类型:必须是同一个对象才为 true
const a = { name: 'Alice' };
const b = { name: 'Alice' };
const c = a;
console.log(a === b); // false(不同对象,即使内容完全相同)
console.log(a === c); // true(同一对象的不同引用)
9.3.3 浅拷贝(Shallow Copy)
浅拷贝创建一个新对象,但只复制第一层属性。如果属性值本身是对象,复制的仍然是引用。
javascript
// 方式1:展开运算符
const original = { name: 'Alice', address: { city: '北京' } };
const shallow = { ...original };
shallow.name = 'Bob'; // 修改原始类型 → 不影响原对象
shallow.address.city = '上海'; // 修改嵌套对象 → 原对象也被影响!
console.log(original.name); // "Alice"(未受影响)
console.log(original.address.city); // "上海"(被影响了)
// 方式2:Object.assign
const shallow2 = Object.assign({}, original);
// 方式3:手动复制
const shallow3 = {};
for (const key in original) {
shallow3[key] = original[key];
}
#mermaid-svg-BK1OZsSgxy0b3xQR{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-BK1OZsSgxy0b3xQR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BK1OZsSgxy0b3xQR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BK1OZsSgxy0b3xQR .error-icon{fill:#552222;}#mermaid-svg-BK1OZsSgxy0b3xQR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BK1OZsSgxy0b3xQR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BK1OZsSgxy0b3xQR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BK1OZsSgxy0b3xQR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BK1OZsSgxy0b3xQR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BK1OZsSgxy0b3xQR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BK1OZsSgxy0b3xQR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BK1OZsSgxy0b3xQR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BK1OZsSgxy0b3xQR .marker.cross{stroke:#333333;}#mermaid-svg-BK1OZsSgxy0b3xQR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BK1OZsSgxy0b3xQR p{margin:0;}#mermaid-svg-BK1OZsSgxy0b3xQR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BK1OZsSgxy0b3xQR .cluster-label text{fill:#333;}#mermaid-svg-BK1OZsSgxy0b3xQR .cluster-label span{color:#333;}#mermaid-svg-BK1OZsSgxy0b3xQR .cluster-label span p{background-color:transparent;}#mermaid-svg-BK1OZsSgxy0b3xQR .label text,#mermaid-svg-BK1OZsSgxy0b3xQR span{fill:#333;color:#333;}#mermaid-svg-BK1OZsSgxy0b3xQR .node rect,#mermaid-svg-BK1OZsSgxy0b3xQR .node circle,#mermaid-svg-BK1OZsSgxy0b3xQR .node ellipse,#mermaid-svg-BK1OZsSgxy0b3xQR .node polygon,#mermaid-svg-BK1OZsSgxy0b3xQR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BK1OZsSgxy0b3xQR .rough-node .label text,#mermaid-svg-BK1OZsSgxy0b3xQR .node .label text,#mermaid-svg-BK1OZsSgxy0b3xQR .image-shape .label,#mermaid-svg-BK1OZsSgxy0b3xQR .icon-shape .label{text-anchor:middle;}#mermaid-svg-BK1OZsSgxy0b3xQR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BK1OZsSgxy0b3xQR .rough-node .label,#mermaid-svg-BK1OZsSgxy0b3xQR .node .label,#mermaid-svg-BK1OZsSgxy0b3xQR .image-shape .label,#mermaid-svg-BK1OZsSgxy0b3xQR .icon-shape .label{text-align:center;}#mermaid-svg-BK1OZsSgxy0b3xQR .node.clickable{cursor:pointer;}#mermaid-svg-BK1OZsSgxy0b3xQR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BK1OZsSgxy0b3xQR .arrowheadPath{fill:#333333;}#mermaid-svg-BK1OZsSgxy0b3xQR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BK1OZsSgxy0b3xQR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BK1OZsSgxy0b3xQR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BK1OZsSgxy0b3xQR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BK1OZsSgxy0b3xQR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BK1OZsSgxy0b3xQR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BK1OZsSgxy0b3xQR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BK1OZsSgxy0b3xQR .cluster text{fill:#333;}#mermaid-svg-BK1OZsSgxy0b3xQR .cluster span{color:#333;}#mermaid-svg-BK1OZsSgxy0b3xQR 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-BK1OZsSgxy0b3xQR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BK1OZsSgxy0b3xQR rect.text{fill:none;stroke-width:0;}#mermaid-svg-BK1OZsSgxy0b3xQR .icon-shape,#mermaid-svg-BK1OZsSgxy0b3xQR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BK1OZsSgxy0b3xQR .icon-shape p,#mermaid-svg-BK1OZsSgxy0b3xQR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BK1OZsSgxy0b3xQR .icon-shape .label rect,#mermaid-svg-BK1OZsSgxy0b3xQR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BK1OZsSgxy0b3xQR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BK1OZsSgxy0b3xQR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BK1OZsSgxy0b3xQR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} shallow(新对象)
original
name: 'Alice'
address →
name: 'Alice'(独立副本)
address →
内存中的 address 对象
city: '北京'
9.3.4 深拷贝(Deep Copy)
深拷贝递归地复制所有层级的对象,确保新对象与原对象完全独立。
javascript
// 方式1:JSON 序列化(简单但有局限)
const original = {
name: 'Alice',
address: { city: '北京', tags: ['VIP', '活跃'] },
score: 95
};
const deep = JSON.parse(JSON.stringify(original));
deep.address.city = '上海';
deep.address.tags.push('新标签');
console.log(original.address.city); // "北京"(未受影响 ✅)
console.log(original.address.tags); // ['VIP', '活跃'](未受影响 ✅)
JSON 深拷贝的局限性:
javascript
const obj = {
date: new Date(),
regex: /test/gi,
fn: () => console.log('hello'),
undef: undefined,
map: new Map(),
circular: null
};
obj.circular = obj; // 循环引用
// ❌ JSON.stringify 无法处理:
// - Date → 变成字符串
// - RegExp → 变成空对象 {}
// - Function → 被忽略
// - undefined → 被忽略
// - Map/Set → 变成空对象 {}
// - 循环引用 → 抛出 TypeError
const bad = JSON.parse(JSON.stringify(obj));
console.log(bad.date instanceof Date); // false(变成了字符串)
console.log(bad.fn); // undefined(函数被忽略了)
总结 :
JSON.parse(JSON.stringify())适合纯数据对象(只包含 number、string、boolean、Array、Object 且无循环引用)。对于包含特殊类型或循环引用的对象,需要手写深拷贝(见实战部分)。
9.3.5 structuredClone(现代方案)
ES2022 引入了 structuredClone,它是浏览器和 Node.js 内置的深拷贝函数,能处理大多数特殊类型和循环引用。
javascript
const original = {
name: 'Alice',
date: new Date(),
regex: /test/gi,
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
nested: { arr: [1, 2, { deep: true }] }
};
// structuredClone 能正确处理
const cloned = structuredClone(original);
console.log(cloned.date instanceof Date); // true ✅
console.log(cloned.regex instanceof RegExp); // true ✅
console.log(cloned.map instanceof Map); // true ✅
console.log(cloned.nested === original.nested); // false ✅(独立副本)
// 甚至能处理循环引用
const circular = { name: 'test' };
circular.self = circular;
const clonedCircular = structuredClone(circular);
console.log(clonedCircular.self === clonedCircular); // true ✅
structuredClone 的局限:
| 能力 | 支持情况 |
|---|---|
| 普通对象、数组 | ✅ |
| Date、RegExp、Map、Set | ✅ |
| ArrayBuffer、TypedArray | ✅ |
| 循环引用 | ✅ |
| Function | ❌ 抛出 DataCloneError |
| DOM 节点 | ❌ 抛出 DataCloneError |
| Symbol 键 | ❌ 不会被复制 |
| 深拷贝方案 | 特殊类型 | 循环引用 | 函数 | 推荐场景 |
|---|---|---|---|---|
JSON.parse(JSON.stringify()) |
❌ | ❌ | ❌ | 纯数据对象(API 数据) |
structuredClone() |
✅ | ✅ | ❌ | 现代环境中的通用方案 |
| 手写深拷贝 | ✅ | ✅ | ✅ | 需要完全控制的场景 |
9.4 垃圾回收机制基础
JavaScript 有自动的垃圾回收(Garbage Collection)机制,开发者不需要手动释放内存。理解垃圾回收的基本原理有助于避免内存泄漏。
9.4.1 可达性(Reachability)
垃圾回收的核心概念是可达性:如果一个值从任何根(Root)出发都无法被引用到,它就是"不可达"的,会被回收。
**根(Roots)**包括:
- 全局变量(
window对象的属性) - 当前函数的局部变量和参数
- 调用栈中其他函数的变量和参数
javascript
// 可达示例
let user = { name: 'Alice' }; // user 变量是根,对象可达
// 让对象变为不可达
user = null; // 原来的 { name: 'Alice' } 不再被引用,会被回收
#mermaid-svg-jtIB6jMpF07j31a6{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-jtIB6jMpF07j31a6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jtIB6jMpF07j31a6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jtIB6jMpF07j31a6 .error-icon{fill:#552222;}#mermaid-svg-jtIB6jMpF07j31a6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jtIB6jMpF07j31a6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jtIB6jMpF07j31a6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jtIB6jMpF07j31a6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jtIB6jMpF07j31a6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jtIB6jMpF07j31a6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jtIB6jMpF07j31a6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jtIB6jMpF07j31a6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jtIB6jMpF07j31a6 .marker.cross{stroke:#333333;}#mermaid-svg-jtIB6jMpF07j31a6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jtIB6jMpF07j31a6 p{margin:0;}#mermaid-svg-jtIB6jMpF07j31a6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jtIB6jMpF07j31a6 .cluster-label text{fill:#333;}#mermaid-svg-jtIB6jMpF07j31a6 .cluster-label span{color:#333;}#mermaid-svg-jtIB6jMpF07j31a6 .cluster-label span p{background-color:transparent;}#mermaid-svg-jtIB6jMpF07j31a6 .label text,#mermaid-svg-jtIB6jMpF07j31a6 span{fill:#333;color:#333;}#mermaid-svg-jtIB6jMpF07j31a6 .node rect,#mermaid-svg-jtIB6jMpF07j31a6 .node circle,#mermaid-svg-jtIB6jMpF07j31a6 .node ellipse,#mermaid-svg-jtIB6jMpF07j31a6 .node polygon,#mermaid-svg-jtIB6jMpF07j31a6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jtIB6jMpF07j31a6 .rough-node .label text,#mermaid-svg-jtIB6jMpF07j31a6 .node .label text,#mermaid-svg-jtIB6jMpF07j31a6 .image-shape .label,#mermaid-svg-jtIB6jMpF07j31a6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-jtIB6jMpF07j31a6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jtIB6jMpF07j31a6 .rough-node .label,#mermaid-svg-jtIB6jMpF07j31a6 .node .label,#mermaid-svg-jtIB6jMpF07j31a6 .image-shape .label,#mermaid-svg-jtIB6jMpF07j31a6 .icon-shape .label{text-align:center;}#mermaid-svg-jtIB6jMpF07j31a6 .node.clickable{cursor:pointer;}#mermaid-svg-jtIB6jMpF07j31a6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jtIB6jMpF07j31a6 .arrowheadPath{fill:#333333;}#mermaid-svg-jtIB6jMpF07j31a6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jtIB6jMpF07j31a6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jtIB6jMpF07j31a6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jtIB6jMpF07j31a6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jtIB6jMpF07j31a6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jtIB6jMpF07j31a6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jtIB6jMpF07j31a6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jtIB6jMpF07j31a6 .cluster text{fill:#333;}#mermaid-svg-jtIB6jMpF07j31a6 .cluster span{color:#333;}#mermaid-svg-jtIB6jMpF07j31a6 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-jtIB6jMpF07j31a6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jtIB6jMpF07j31a6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-jtIB6jMpF07j31a6 .icon-shape,#mermaid-svg-jtIB6jMpF07j31a6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jtIB6jMpF07j31a6 .icon-shape p,#mermaid-svg-jtIB6jMpF07j31a6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jtIB6jMpF07j31a6 .icon-shape .label rect,#mermaid-svg-jtIB6jMpF07j31a6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jtIB6jMpF07j31a6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jtIB6jMpF07j31a6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jtIB6jMpF07j31a6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 根(全局变量/局部变量)
user 变量
对象 { name: 'Alice' }
嵌套对象 { city: '北京' }
其他变量
其他对象
9.4.2 垃圾回收算法:标记-清除
现代 JavaScript 引擎主要使用标记-清除(Mark-and-Sweep)算法:
- 标记阶段:从根出发,递归标记所有可达的对象
- 清除阶段:遍历堆内存,删除所有未被标记的对象
- 整理阶段:将存活对象紧凑排列,减少内存碎片
#mermaid-svg-UfJau9gxwNFKYcXK{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-UfJau9gxwNFKYcXK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UfJau9gxwNFKYcXK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UfJau9gxwNFKYcXK .error-icon{fill:#552222;}#mermaid-svg-UfJau9gxwNFKYcXK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UfJau9gxwNFKYcXK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UfJau9gxwNFKYcXK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UfJau9gxwNFKYcXK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UfJau9gxwNFKYcXK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UfJau9gxwNFKYcXK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UfJau9gxwNFKYcXK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UfJau9gxwNFKYcXK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UfJau9gxwNFKYcXK .marker.cross{stroke:#333333;}#mermaid-svg-UfJau9gxwNFKYcXK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UfJau9gxwNFKYcXK p{margin:0;}#mermaid-svg-UfJau9gxwNFKYcXK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UfJau9gxwNFKYcXK .cluster-label text{fill:#333;}#mermaid-svg-UfJau9gxwNFKYcXK .cluster-label span{color:#333;}#mermaid-svg-UfJau9gxwNFKYcXK .cluster-label span p{background-color:transparent;}#mermaid-svg-UfJau9gxwNFKYcXK .label text,#mermaid-svg-UfJau9gxwNFKYcXK span{fill:#333;color:#333;}#mermaid-svg-UfJau9gxwNFKYcXK .node rect,#mermaid-svg-UfJau9gxwNFKYcXK .node circle,#mermaid-svg-UfJau9gxwNFKYcXK .node ellipse,#mermaid-svg-UfJau9gxwNFKYcXK .node polygon,#mermaid-svg-UfJau9gxwNFKYcXK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UfJau9gxwNFKYcXK .rough-node .label text,#mermaid-svg-UfJau9gxwNFKYcXK .node .label text,#mermaid-svg-UfJau9gxwNFKYcXK .image-shape .label,#mermaid-svg-UfJau9gxwNFKYcXK .icon-shape .label{text-anchor:middle;}#mermaid-svg-UfJau9gxwNFKYcXK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UfJau9gxwNFKYcXK .rough-node .label,#mermaid-svg-UfJau9gxwNFKYcXK .node .label,#mermaid-svg-UfJau9gxwNFKYcXK .image-shape .label,#mermaid-svg-UfJau9gxwNFKYcXK .icon-shape .label{text-align:center;}#mermaid-svg-UfJau9gxwNFKYcXK .node.clickable{cursor:pointer;}#mermaid-svg-UfJau9gxwNFKYcXK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UfJau9gxwNFKYcXK .arrowheadPath{fill:#333333;}#mermaid-svg-UfJau9gxwNFKYcXK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UfJau9gxwNFKYcXK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UfJau9gxwNFKYcXK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UfJau9gxwNFKYcXK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UfJau9gxwNFKYcXK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UfJau9gxwNFKYcXK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UfJau9gxwNFKYcXK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UfJau9gxwNFKYcXK .cluster text{fill:#333;}#mermaid-svg-UfJau9gxwNFKYcXK .cluster span{color:#333;}#mermaid-svg-UfJau9gxwNFKYcXK 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-UfJau9gxwNFKYcXK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UfJau9gxwNFKYcXK rect.text{fill:none;stroke-width:0;}#mermaid-svg-UfJau9gxwNFKYcXK .icon-shape,#mermaid-svg-UfJau9gxwNFKYcXK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UfJau9gxwNFKYcXK .icon-shape p,#mermaid-svg-UfJau9gxwNFKYcXK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UfJau9gxwNFKYcXK .icon-shape .label rect,#mermaid-svg-UfJau9gxwNFKYcXK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UfJau9gxwNFKYcXK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UfJau9gxwNFKYcXK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UfJau9gxwNFKYcXK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 标记阶段
2. 清除阶段
3. 整理阶段
从根出发,标记所有可达对象
删除未标记的对象
紧凑排列存活对象
9.4.3 常见内存泄漏场景
虽然 JavaScript 有自动垃圾回收,但不当的代码仍然会导致内存泄漏------对象不再使用但依然被引用,无法被回收。
javascript
// 1. 意外的全局变量
function leak() {
// 忘记声明 let/const,在非严格模式下创建全局变量
leakedData = []; // 相当于 window.leakedData = []
}
// 2. 未清理的定时器
const element = document.getElementById('myDiv');
setInterval(() => {
element.textContent = Date.now(); // element 被闭包引用,无法被回收
}, 1000);
// 如果 element 从 DOM 中移除,定时器仍在运行 → 内存泄漏
// 3. 未清理的事件监听
const button = document.getElementById('btn');
button.addEventListener('click', function handler() {
// handler 引用了 button 所在的作用域中的大型数据
});
// button 从 DOM 移除后,如果没有 removeEventListener,可能导致泄漏
// 4. 闭包持有大对象引用
function processData() {
const hugeData = new Array(1000000); // 大数据
return function() {
console.log(hugeData.length); // 闭包持有 hugeData 的引用
};
}
const processor = processData();
// 即使 hugeData 不再需要,闭包依然持有它的引用
最佳实践 :不再使用的大对象及时设为
null,定时器和事件监听在组件销毁时清理,避免闭包无意中持有大数据的引用。
9.5 Object 静态方法
Object 构造函数提供了许多实用的静态方法,用于操作和查询对象。
9.5.1 Object.keys / values / entries
这三个方法是遍历对象最常用的工具。
javascript
const user = {
name: 'Alice',
age: 25,
city: '北京'
};
// Object.keys → 属性名数组
console.log(Object.keys(user));
// ['name', 'age', 'city']
// Object.values → 属性值数组
console.log(Object.values(user));
// ['Alice', 25, '北京']
// Object.entries → 键值对数组
console.log(Object.entries(user));
// [['name', 'Alice'], ['age', 25], ['city', '北京']]
实际应用:
javascript
const prices = { apple: 10, banana: 6, orange: 8 };
// 过滤价格大于 7 的水果
const expensive = Object.entries(prices)
.filter(([fruit, price]) => price > 7)
.map(([fruit]) => fruit);
console.log(expensive); // ['apple', 'orange']
// 将对象转换为排序后的新对象
const sorted = Object.fromEntries(
Object.entries(prices).sort((a, b) => a[1] - b[1])
);
console.log(sorted); // { banana: 6, orange: 8, apple: 10 }
// 统计对象属性个数
const count = Object.keys(user).length;
console.log(count); // 3
9.5.2 Object.assign:对象合并
Object.assign(target, ...sources) 将一个或多个源对象的可枚举属性复制到目标对象,并返回目标对象。
javascript
const target = { a: 1, b: 2 };
const source1 = { b: 3, c: 4 };
const source2 = { c: 5, d: 6 };
const result = Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 3, c: 5, d: 6 }
console.log(result); // 与 target 是同一个对象(=== 为 true)
注意 :
Object.assign会修改目标对象 。如果不想修改原对象,传入空对象{}作为目标。
javascript
const original = { a: 1, b: 2 };
// ❌ 修改了 original
Object.assign(original, { c: 3 });
console.log(original); // { a: 1, b: 2, c: 3 }
// ✅ 不修改 original,创建新对象
const merged = Object.assign({}, original, { d: 4 });
console.log(original); // { a: 1, b: 2, c: 3 }(不含 d)
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
Object.assign 是浅拷贝:
javascript
const source = { nested: { value: 1 } };
const target = Object.assign({}, source);
target.nested.value = 999;
console.log(source.nested.value); // 999(嵌套对象是共享引用)
9.5.3 Object.freeze / seal / preventExtensions
这三个方法用于限制对象的修改能力,提供不同程度的"冻结"。
javascript
// Object.freeze:完全冻结(不能添加、删除、修改属性)
const frozen = Object.freeze({ name: 'Alice', age: 25 });
frozen.name = 'Bob'; // 静默失败(严格模式下报错)
frozen.email = 'a@b.com'; // 静默失败
delete frozen.age; // 静默失败
console.log(frozen); // { name: 'Alice', age: 25 }(完全不变)
// Object.seal:密封(不能添加、删除属性,但可以修改已有属性)
const sealed = Object.seal({ name: 'Alice', age: 25 });
sealed.name = 'Bob'; // ✅ 可以修改
sealed.email = 'a@b.com'; // 静默失败
delete sealed.age; // 静默失败
console.log(sealed); // { name: 'Bob', age: 25 }
// Object.preventExtensions:阻止添加新属性(但可以修改和删除已有属性)
const ext = Object.preventExtensions({ name: 'Alice', age: 25 });
ext.name = 'Bob'; // ✅ 可以修改
ext.email = 'a@b.com'; // 静默失败
delete ext.age; // ✅ 可以删除
console.log(ext); // { name: 'Bob' }
| 方法 | 添加属性 | 删除属性 | 修改属性 | 典型用途 |
|---|---|---|---|---|
Object.freeze |
❌ | ❌ | ❌ | 常量配置、不可变数据 |
Object.seal |
❌ | ❌ | ✅ | 固定结构的对象 |
Object.preventExtensions |
❌ | ✅ | ✅ | 防止意外添加属性 |
注意 :这三个方法都是浅冻结------只影响对象的第一层属性,嵌套对象不受影响。
javascript
const config = Object.freeze({
theme: 'dark',
database: {
host: 'localhost',
port: 3306
}
});
config.theme = 'light'; // 静默失败 ✅(第一层被冻结)
config.database.host = 'remote'; // ✅ 成功!(嵌套对象未被冻结)
console.log(config.database.host); // "remote"
9.5.4 其他常用 Object 方法
javascript
const obj = { a: 1, b: 2, c: 3 };
// Object.hasOwn:检查自身属性(替代 hasOwnProperty)
console.log(Object.hasOwn(obj, 'a')); // true
console.log(Object.hasOwn(obj, 'toString')); // false
// Object.fromEntries:从键值对数组创建对象(entries 的逆操作)
const entries = [['name', 'Alice'], ['age', 25]];
const user = Object.fromEntries(entries);
console.log(user); // { name: 'Alice', age: 25 }
// Object.is:精确相等比较(比 === 更精确)
console.log(Object.is(NaN, NaN)); // true(=== 返回 false)
console.log(Object.is(0, -0)); // false(=== 返回 true)
console.log(Object.is(1, 1)); // true
console.log(Object.is(null, null)); // true
// Object.getPrototypeOf:获取对象的原型
console.log(Object.getPrototypeOf([]) === Array.prototype); // true
9.6 实战:对象合并与深拷贝实现
9.6.1 实战1:智能对象合并
实现一个 deepMerge 函数,递归合并两个对象,而不是简单覆盖。
javascript
/**
* 深度合并两个对象
* - 相同属性:源对象覆盖目标对象
* - 嵌套对象:递归合并(而非覆盖)
* - 数组:源对象覆盖目标对象
* @param {Object} target - 目标对象
* @param {Object} source - 源对象
* @returns {Object} 合并后的新对象
*/
function deepMerge(target, source) {
const result = { ...target };
for (const key of Object.keys(source)) {
const targetVal = target[key];
const sourceVal = source[key];
// 如果两边都是纯对象(非数组),递归合并
if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {
result[key] = deepMerge(targetVal, sourceVal);
} else {
result[key] = sourceVal;
}
}
return result;
}
/**
* 判断是否为纯对象(排除数组、Date、RegExp 等)
*/
function isPlainObject(value) {
return (
value !== null &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.getPrototypeOf(value) === Object.prototype
);
}
// 测试
const defaultConfig = {
server: {
host: 'localhost',
port: 3000,
ssl: false
},
database: {
host: 'localhost',
port: 3306,
name: 'mydb'
},
debug: false
};
const userConfig = {
server: {
port: 8080,
ssl: true
},
database: {
host: '192.168.1.100'
},
debug: true
};
const config = deepMerge(defaultConfig, userConfig);
console.log(config);
// {
// server: { host: 'localhost', port: 8080, ssl: true }, ← 递归合并
// database: { host: '192.168.1.100', port: 3306, name: 'mydb' }, ← 递归合并
// debug: true
// }
9.6.2 实战2:手写深拷贝
实现一个支持循环引用的深拷贝函数。
javascript
/**
* 手写深拷贝
* 支持:普通对象、数组、Date、RegExp、Map、Set、循环引用
* @param {*} obj - 要拷贝的值
* @param {WeakMap} cache - 缓存(处理循环引用)
* @returns {*} 深拷贝后的值
*/
function deepClone(obj, cache = new WeakMap()) {
// 1. 原始类型和函数直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 2. 处理循环引用
if (cache.has(obj)) {
return cache.get(obj);
}
// 3. 处理特殊类型
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 4. 创建新对象/数组
const clone = Array.isArray(obj) ? [] : {};
// 5. 记录到缓存(必须在递归之前,防止循环引用导致无限递归)
cache.set(obj, clone);
// 6. 处理 Map
if (obj instanceof Map) {
obj.forEach((value, key) => {
clone.set(deepClone(key, cache), deepClone(value, cache));
});
return clone;
}
// 7. 处理 Set
if (obj instanceof Set) {
obj.forEach(value => {
clone.add(deepClone(value, cache));
});
return clone;
}
// 8. 递归复制普通对象/数组的属性
for (const key of Object.keys(obj)) {
clone[key] = deepClone(obj[key], cache);
}
return clone;
}
// ===== 测试 =====
// 测试1:嵌套对象
const original = {
name: 'Alice',
address: { city: '北京', tags: ['VIP'] },
scores: [90, 85, 92]
};
const cloned = deepClone(original);
cloned.address.city = '上海';
cloned.address.tags.push('新标签');
cloned.scores[0] = 100;
console.log(original.address.city); // "北京" ✅(独立副本)
console.log(original.address.tags); // ['VIP'] ✅(独立副本)
console.log(original.scores); // [90, 85, 92] ✅(独立副本)
// 测试2:循环引用
const circular = { name: 'test' };
circular.self = circular;
const clonedCircular = deepClone(circular);
console.log(clonedCircular.name); // "test" ✅
console.log(clonedCircular.self === clonedCircular); // true ✅
console.log(clonedCircular.self === circular); // false ✅(不是同一个对象)
// 测试3:特殊类型
const special = {
date: new Date('2024-01-01'),
regex: /hello/gi,
map: new Map([['key1', { nested: true }]]),
set: new Set([1, 2, 3])
};
const clonedSpecial = deepClone(special);
console.log(clonedSpecial.date instanceof Date); // true ✅
console.log(clonedSpecial.date === special.date); // false ✅(独立副本)
console.log(clonedSpecial.regex instanceof RegExp); // true ✅
console.log(clonedSpecial.map instanceof Map); // true ✅
9.6.3 深拷贝实现要点解析
#mermaid-svg-kUPdcDxBR7G19Rrb{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-kUPdcDxBR7G19Rrb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kUPdcDxBR7G19Rrb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kUPdcDxBR7G19Rrb .error-icon{fill:#552222;}#mermaid-svg-kUPdcDxBR7G19Rrb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kUPdcDxBR7G19Rrb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kUPdcDxBR7G19Rrb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kUPdcDxBR7G19Rrb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kUPdcDxBR7G19Rrb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kUPdcDxBR7G19Rrb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kUPdcDxBR7G19Rrb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kUPdcDxBR7G19Rrb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kUPdcDxBR7G19Rrb .marker.cross{stroke:#333333;}#mermaid-svg-kUPdcDxBR7G19Rrb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kUPdcDxBR7G19Rrb p{margin:0;}#mermaid-svg-kUPdcDxBR7G19Rrb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kUPdcDxBR7G19Rrb .cluster-label text{fill:#333;}#mermaid-svg-kUPdcDxBR7G19Rrb .cluster-label span{color:#333;}#mermaid-svg-kUPdcDxBR7G19Rrb .cluster-label span p{background-color:transparent;}#mermaid-svg-kUPdcDxBR7G19Rrb .label text,#mermaid-svg-kUPdcDxBR7G19Rrb span{fill:#333;color:#333;}#mermaid-svg-kUPdcDxBR7G19Rrb .node rect,#mermaid-svg-kUPdcDxBR7G19Rrb .node circle,#mermaid-svg-kUPdcDxBR7G19Rrb .node ellipse,#mermaid-svg-kUPdcDxBR7G19Rrb .node polygon,#mermaid-svg-kUPdcDxBR7G19Rrb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kUPdcDxBR7G19Rrb .rough-node .label text,#mermaid-svg-kUPdcDxBR7G19Rrb .node .label text,#mermaid-svg-kUPdcDxBR7G19Rrb .image-shape .label,#mermaid-svg-kUPdcDxBR7G19Rrb .icon-shape .label{text-anchor:middle;}#mermaid-svg-kUPdcDxBR7G19Rrb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kUPdcDxBR7G19Rrb .rough-node .label,#mermaid-svg-kUPdcDxBR7G19Rrb .node .label,#mermaid-svg-kUPdcDxBR7G19Rrb .image-shape .label,#mermaid-svg-kUPdcDxBR7G19Rrb .icon-shape .label{text-align:center;}#mermaid-svg-kUPdcDxBR7G19Rrb .node.clickable{cursor:pointer;}#mermaid-svg-kUPdcDxBR7G19Rrb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kUPdcDxBR7G19Rrb .arrowheadPath{fill:#333333;}#mermaid-svg-kUPdcDxBR7G19Rrb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kUPdcDxBR7G19Rrb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kUPdcDxBR7G19Rrb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kUPdcDxBR7G19Rrb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kUPdcDxBR7G19Rrb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kUPdcDxBR7G19Rrb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kUPdcDxBR7G19Rrb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kUPdcDxBR7G19Rrb .cluster text{fill:#333;}#mermaid-svg-kUPdcDxBR7G19Rrb .cluster span{color:#333;}#mermaid-svg-kUPdcDxBR7G19Rrb 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-kUPdcDxBR7G19Rrb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kUPdcDxBR7G19Rrb rect.text{fill:none;stroke-width:0;}#mermaid-svg-kUPdcDxBR7G19Rrb .icon-shape,#mermaid-svg-kUPdcDxBR7G19Rrb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kUPdcDxBR7G19Rrb .icon-shape p,#mermaid-svg-kUPdcDxBR7G19Rrb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kUPdcDxBR7G19Rrb .icon-shape .label rect,#mermaid-svg-kUPdcDxBR7G19Rrb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kUPdcDxBR7G19Rrb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kUPdcDxBR7G19Rrb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kUPdcDxBR7G19Rrb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
Date
RegExp
Map/Set
普通对象/数组
deepClone(obj)
typeof !== 'object'
或 null?
直接返回原始值
cache 中存在?
返回缓存的副本
(处理循环引用)
特殊类型?
new Date(time)
new RegExp(source, flags)
创建新 Map/Set
递归复制元素
创建新 {}/\[\]
cache.set(obj, clone)
递归复制每个属性
为什么用 WeakMap 做缓存? WeakMap 的键是弱引用,不会阻止垃圾回收。如果用普通 Map,被拷贝的对象在函数结束后仍然被 Map 引用,无法被垃圾回收,造成内存泄漏。
本章小结
| 知识点 | 要点 |
|---|---|
| 对象字面量 | {} 创建对象,推荐方式,支持属性简写和计算属性名 |
new Object() |
构造函数方式,冗长不推荐 |
点号 . |
访问已知属性,语法简洁 |
方括号 [] |
访问动态键或含特殊字符的键 |
delete |
删除对象属性,成功返回 true |
in 操作符 |
检查属性是否存在(含原型链) |
Object.hasOwn |
只检查自身属性(现代推荐写法) |
| 引用赋值 | 对象赋值复制的是引用,不是值 |
| 浅拷贝 | { ...obj } 或 Object.assign,只复制第一层 |
| 深拷贝 | structuredClone 或手写递归,所有层级独立 |
| 垃圾回收 | 标记-清除算法,不可达的对象会被自动回收 |
Object.keys/values/entries |
获取对象的属性名、属性值、键值对数组 |
Object.assign |
合并对象(浅拷贝),修改目标对象 |
Object.freeze |
冻结对象,不能增删改属性(浅冻结) |
一句话总结 :对象是 JavaScript 的"万能容器"。理解引用 的本质是避免 Bug 的关键------赋值只是复制地址,浅拷贝只复制第一层,深拷贝才能真正独立。
Object.keys/values/entries三件套是遍历对象的标准工具,structuredClone是现代环境中最方便的深拷贝方案。
下篇预告 :第10章:对象方法与 this --- 我们将学习如何在对象中定义方法,深入理解 this 的四种绑定规则,以及 call / apply / bind 的原理和使用。
作者 :xhmico | CSDN 博客 :https://blog.csdn.net/xhmico
参考资源 :The Modern JavaScript Tutorial | MDN Web Docs | ECMAScript 规范