浅拷贝 (Shallow Copy)
本质:只复制一层,共享深层。
浅拷贝的本质是创建一个新的对象/数组,然后将原始对象/数组的“第一层”属性值复制到新对象/数组中。
-
如果第一层的属性值是值类型(
string
,number
等),那么就直接复制这个值。 -
如果第一层的属性值是引用类型(另一个对象或数组),那么就只复制这个引用(内存地址)。
这意味着,新对象和旧对象的第一层是独立的,但它们共享了所有深层次的对象。
一个生动的比喻:
想象你有一个工具箱(原始对象),里面装着螺丝刀(值类型)和一个装满钉子的盒子(引用类型)。
-
浅拷贝就像是你买了一个新的空工具箱(新对象),然后:
-
把你原来的螺丝刀拿出来,买一把一模一样的新螺丝刀放进新工具箱。
-
把你原来的那个装钉子的盒子直接拿过来,也放进新工具箱。
-
-
结果:你现在有两个工具箱。你用新工具箱里的螺丝刀,不会影响旧的。但是,两个工具箱里装的是同一个钉子盒。你从任何一个工具箱里拿出钉子用掉,另一个工具箱里的钉子也会变少。
浅拷贝的实现方法及示例
常见的浅拷贝方法有 Object.assign()
和扩展运算符 ...
。
const original = {
name: "My Component", // 值类型
version: 1.0, // 值类型
config: { // 引用类型
theme: "dark",
permissions: ["read", "write"]
}
};
// 使用扩展运算符进行浅拷贝
const shallowCopy = { ...original };
// 1. 修改顶层的值类型属性
shallowCopy.version = 1.1;
console.log(original.version); // 1.0 (原始对象不受影响,因为 version 是值类型)
console.log(shallowCopy.version); // 1.1
// 2. 修改嵌套的引用类型属性
shallowCopy.config.theme = "light";
console.log(original.config.theme); // "light" (⚠️ 原始对象被意外修改了!)
console.log(shallowCopy.config.theme); // "light"
// 验证:它们共享同一个 config 对象
console.log(original.config === shallowCopy.config); // true
深拷贝 (Deep Copy)
本质:彻底复制,完全独立。
深拷贝的本质是创建一个全新的、完全独立的对象/数组副本。它会递归地遍历原始对象的所有属性,直到最深层:
-
每当遇到一个引用类型(对象或数组),它都会创建一个新的对象或数组,然后把内容复制过去,而不是只复制引用。
-
对于值类型,它和浅拷贝一样,直接复制值。
继续上面的比喻:
-
深拷贝就像是你不仅买了一个新的空工具箱,而且:
-
买了一把一模一样的新螺丝刀放进去。
-
还买了一个新的空钉子盒,然后把你旧钉子盒里的钉子一个一个地复制到这个新盒子里。
-
-
结果:你现在有两个完全独立的工具箱。它们里面的所有东西(包括钉子盒)都是独立的。你对其中任何一个的任何操作,都不会影响另一个。
深拷贝的实现方法及示例
1. JSON.parse(JSON.stringify(object))
(简单但不完美)
这是最简单快捷的方法,但有几个致命缺陷:
-
会忽略
undefined
,Symbol
和函数。 -
不能处理循环引用(对象内部属性互相引用),会报错。
-
会把
Date
对象转换成字符串。
const original = {
name: "My App",
createdAt: new Date(),
update: function() { console.log('update'); },
config: {
id: Symbol('id')
}
};
const jsonCopy = JSON.parse(JSON.stringify(original));
console.log(jsonCopy);
/* 输出:
{
"name": "My App",
"createdAt": "2023-10-27T...", // Date 变成了字符串
"config": {} // update 函数和 symbol 属性都丢失了
}
*/
2. structuredClone()
(现代浏览器的标准答案)
这是一个新的 Web API,专门用来进行深拷贝。它功能强大,能处理循环引用和更多的数据类型(如 Date
, RegExp
, Map
, Set
等)。是目前首选的内置方法。
const original = {
name: "My Component",
config: {
theme: "dark"
}
};
original.self = original; // 创建一个循环引用
const deepCopy = structuredClone(original);
// 修改嵌套属性
deepCopy.config.theme = "light";
console.log(original.config.theme); // "dark" (原始对象完全不受影响)
console.log(deepCopy.config.theme); // "light"
// 验证:它们不共享任何内部对象
console.log(original.config === deepCopy.config); // false
console.log(original === deepCopy); // false
注意:structuredClone
也不能拷贝函数和 Symbol
属性。
3. 使用库(如 Lodash 的 _.cloneDeep()
)
在 structuredClone
出现之前以及在需要兼容旧环境或拷贝函数等复杂场景时,库函数是最可靠的选择。
总结:本质区别的核心
特性 | 浅拷贝 (Shallow Copy) | 深拷贝 (Deep Copy) |
---|---|---|
核心本质 | 创建新对象,但共享内部的引用类型数据 | 创建新对象,并递归地复制所有内部数据,完全独立 |
复制层级 | 只复制第一层 | 递归复制所有层级 |
内存关系 | 新旧对象中的引用类型属性指向同一块堆内存 | 新旧对象完全独立,指向不同的堆内存 |
适用场景 | 当对象只有一层,或你明确知道不需要修改嵌套对象时 | 当你需要一个对象的完整、独立的快照,以避免修改原始数据时 |
性能 | 快,开销小 | 慢,开销大,因为需要遍历和创建更多对象 |
面试时,能够清晰地解释出“浅拷贝只复制引用,深拷贝递归创建新对象”,并用“工具箱和钉子盒”这样的比喻来辅助说明,就能充分展示你对这个概念的深刻理解。