在 JavaScript 开发中,深拷贝(Deep Copy) 和 浅拷贝(Shallow Copy) 是处理对象和数组时必须掌握的核心概念。它们决定了新对象是否与原对象共享内存引用,直接影响程序的行为和数据安全。
一、什么是浅拷贝?什么是深拷贝?
1. 浅拷贝(Shallow Copy)
- 定义:创建一个新对象,但只复制原对象第一层属性的值。
- 对于基本数据类型(
number、string、boolean),复制的是值。 - 对于引用类型(
object、array),复制的是内存地址(引用),新旧对象的嵌套对象仍指向同一块内存。 - 结果:修改嵌套对象会影响原对象。
2. 深拷贝(Deep Copy)
- 定义:创建一个新对象,并递归复制原对象的所有层级,包括所有嵌套对象和数组。
- 新对象与原对象完全独立,互不影响。
- 结果:修改新对象不会影响原对象。
二、使用 Object.assign() 实现浅拷贝
Object.assign(target, ...sources) 是 ES6 提供的方法,常用于对象合并和浅拷贝。
示例:浅拷贝
const original = {
name: "Alice",
age: 25,
address: {
city: "Beijing",
zip: "100000"
},
hobbies: ["reading", "music"]
};
// 使用 Object.assign() 创建浅拷贝
const shallowCopy = Object.assign({}, original);
// 修改浅拷贝的嵌套对象
shallowCopy.address.city = "Shanghai";
shallowCopy.hobbies.push("travel");
console.log(original.address.city); // "Shanghai" ← 原对象也被修改了!
console.log(original.hobbies); // ["reading", "music", "travel"] ← 原数组也被修改了!
原因分析
Object.assign()只复制了address和hobbies的引用,没有复制它们指向的对象。- 所以
shallowCopy.address和original.address指向同一个对象,修改一个会影响另一个。
适用场景
- 只需复制对象第一层属性。
- 性能要求高,且确认不会修改嵌套结构。
三、使用 JSON.parse(JSON.stringify()) 实现深拷贝
这是一种“取巧”的深拷贝方法,利用 JSON 序列化和反序列化实现。
示例:深拷贝
const original = {
name: "Bob",
age: 30,
address: {
city: "Shanghai",
zip: "200000"
},
hobbies: ["sports", "coding"]
};
// 使用 JSON 方法实现深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));
// 修改深拷贝的嵌套对象
deepCopy.address.city = "Guangzhou";
deepCopy.hobbies.push("gaming");
console.log(original.address.city); // "Shanghai" ← 原对象未受影响 ✅
console.log(original.hobbies); // ["sports", "coding"] ← 原数组未受影响 ✅
原理
JSON.stringify(original):将对象序列化为 JSON 字符串,过程中会递归遍历所有可枚举属性。JSON.parse(...):将字符串反序列化为新对象,生成全新的对象结构。
由于序列化过程会“扁平化”对象,反序列化时重建,因此新旧对象完全独立。
四、JSON.parse(JSON.stringify()) 的局限性(重要!)
虽然这种方法简单有效,但存在严重限制,不能用于所有场景:
1. 无法处理函数、undefined、Symbol
const obj = {
func: function() { console.log("hello"); },
value: undefined,
sym: Symbol("id")
};
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.func); // undefined
console.log(copied.value); // undefined(消失)
console.log(copied.sym); // undefined
原因:JSON 格式不支持函数、
undefined和Symbol。
2. 无法处理 Date 对象
const obj = { date: new Date() };
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.date); // "2025-08-12T15:30:00.000Z" ← 字符串!
console.log(typeof copied.date); // "string"
问题:
Date被转为字符串,不再是Date实例。
3. 无法处理 RegExp、Error 等特殊对象
const obj = { regex: /abc/i };
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.regex); // {} ← 变成空对象!
4. 无法处理循环引用(Circular Reference)
const obj = { name: "test" };
obj.self = obj; // 循环引用
JSON.stringify(obj); // TypeError: Converting circular structure to JSON
5. 会忽略 Map、Set、WeakMap、WeakSet 等
const obj = { map: new Map([["key", "value"]]) };
const copied = JSON.parse(JSON.stringify(obj));
console.log(copied.map); // {} ← 变成普通对象
五、对比总结
| 方法 | 类型 | 是否深拷贝 | 支持函数 | 支持 Date | 支持循环引用 | 性能 |
|---|---|---|---|---|---|---|
Object.assign() | 浅拷贝 | NO | Yes | Yes(引用) | Yes | 高 |
JSON.parse(JSON.stringify()) | 深拷贝 | Yes(有限) | NO | NO(变字符串) | (报错) | 中等(序列化开销) |
六、何时使用哪种方法?
| 场景 | 推荐方法 |
|---|---|
| 复制简单对象,无嵌套引用 | Object.assign() |
复制纯数据对象(POJO),无函数、Date、循环引用 | JSON.parse(JSON.stringify()) |
| 需要完整深拷贝(支持所有类型) | 使用专业库(如 lodash.cloneDeep)或手写递归深拷贝 |
| 高频操作,性能敏感 | Object.assign() 或结构赋值 {...obj} |
七、推荐的深拷贝方案
方案 1:使用 Lodash
npm install lodash
import { cloneDeep } from 'lodash';
const deepCopy = cloneDeep(original); // 支持所有类型,包括循环引用
方案 2:手写递归深拷贝(简化版)
function deepClone(obj, visited = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (visited.has(obj)) return visited.get(obj);
let clone;
if (obj instanceof Date) {
clone = new Date(obj);
} else if (obj instanceof RegExp) {
clone = new RegExp(obj);
} else if (Array.isArray(obj)) {
clone = obj.map(item => deepClone(item, visited));
} else {
clone = {};
visited.set(obj, clone);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], visited);
}
}
}
visited.set(obj, clone);
return clone;
}
八、总结
Object.assign()是浅拷贝:只复制第一层,嵌套对象仍共享引用。JSON.parse(JSON.stringify())是“伪深拷贝”:能处理嵌套对象,但有诸多限制(不支持函数、Date、循环引用等)。- 选择拷贝方式时,必须考虑数据结构的复杂性。
- 生产环境推荐使用
lodash.cloneDeep或其他成熟库,避免踩坑。
核心原则:
- 简单数据 →
Object.assign()或JSON方法。- 复杂数据、不确定结构 → 专业深拷贝工具。
- 永远不要对包含函数或循环引用的对象使用
JSON方法!