万字长文:手撕JS深浅拷贝完全指南
在JavaScript中,数据的复制是一个重要而又复杂的话题。理解深拷贝和浅拷贝的区别,以及它们的实现方式,对开发者来说至关重要。本文将深入探讨这两个概念,并提供详细的案例和场景,帮助你全面掌握深浅拷贝的技巧与应用。
目录
-
- 4.1 Object.assign()
- 4.2 Array.prototype.slice()
- 4.3 Spread运算符
- 4.4 JSON.parse(JSON.stringify())
- 4.5 递归实现深拷贝
什么是浅拷贝
浅拷贝是指创建一个新对象,该对象的属性是原对象属性的引用(即指向同一内存地址)。也就是说,浅拷贝只复制对象的第一层属性,对于嵌套对象,仅复制其引用。
1.1 浅拷贝的实现方式
在JavaScript中,有几种常见的方法可以实现浅拷贝:
Object.assign()- 扩展运算符 (
...) Array.prototype.slice()Array.prototype.concat()
1.2 浅拷贝的示例
下面是一个使用 Object.assign() 实现浅拷贝的简单示例:
javascriptCopy Codeconst original = {
name: 'Alice',
age: 25,
address: {
city: 'New York',
zip: '10001'
}
};
const shallowCopy = Object.assign({}, original);
// 修改浅拷贝的属性
shallowCopy.name = 'Bob';
shallowCopy.address.city = 'Los Angeles';
console.log(original.name); // Alice
console.log(original.address.city); // Los Angeles
在这个例子中,修改 shallowCopy 对象的 name 属性并不会影响 original 对象,但修改 address.city 属性却会影响 original 对象,因为 address 是一个嵌套对象,它们指向同一内存地址。
1.3 浅拷贝的使用场景
浅拷贝适用于以下场景:
- 当你只需要复制对象的第一层属性时。
- 当对象的嵌套属性不需要被改变时。
- 性能要求高且对象结构较为简单时。
什么是深拷贝
深拷贝则是指创建一个新对象,递归地复制所有层级的属性,包括嵌套对象的属性。深拷贝会生成一个完全独立的对象,即使是嵌套对象的属性也会被复制。
2.1 深拷贝的实现方式
深拷贝的常用实现方式包括:
- 使用
JSON.parse(JSON.stringify()) - 使用递归函数
- 使用第三方库如 Lodash 的
_.cloneDeep()
2.2 深拷贝的示例
下面是一个使用 JSON.parse(JSON.stringify()) 实现深拷贝的示例:
javascriptCopy Codeconst original = {
name: 'Alice',
age: 25,
address: {
city: 'New York',
zip: '10001'
}
};
const deepCopy = JSON.parse(JSON.stringify(original));
// 修改深拷贝的属性
deepCopy.name = 'Bob';
deepCopy.address.city = 'Los Angeles';
console.log(original.name); // Alice
console.log(original.address.city); // New York
在这个例子中,修改 deepCopy 对象的任何属性都不会影响 original 对象。deepCopy 完全独立于 original。
2.3 深拷贝的使用场景
深拷贝适用于以下场景:
- 当对象包含嵌套对象或数组时。
- 当你需要确保对原始对象没有副作用时。
- 当需要在操作对象时保证数据的完整性时。
浅拷贝与深拷贝的对比
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制深度 | 仅复制第一层 | 递归复制所有层 |
| 引用关系 | 嵌套对象引用相同 | 嵌套对象是新对象 |
| 性能 | 通常较快 | 通常较慢 |
| 使用场景 | 简单对象或独立属性 | 复杂对象或需要完整独立数据 |
常见的拷贝方法总结
4.1 Object.assign()
Object.assign() 方法用于将一个或多个源对象的可枚举属性复制到目标对象中。它实现的是浅拷贝。
javascriptCopy Codeconst target = {};
const source = { a: 1, b: { c: 2 } };
Object.assign(target, source);
target.a = 3; // 不影响 source
target.b.c = 4; // 影响 source.b.c
4.2 Array.prototype.slice()
slice() 方法返回一个对象的浅拷贝。
javascriptCopy Codeconst arr = [1, 2, { a: 3 }];
const shallowCopyArr = arr.slice();
shallowCopyArr[0] = 0; // 不影响 arr
shallowCopyArr[2].a = 4; // 影响 arr[2].a
4.3 Spread运算符
扩展运算符 (...) 可用于对象和数组的浅拷贝。
javascriptCopy Codeconst obj = { x: 1, y: { z: 2 } };
const shallowCopyObj = { ...obj };
shallowCopyObj.y.z = 3; // 影响 obj.y.z
4.4 JSON.parse(JSON.stringify())
该方法可以实现深拷贝,但有限制,如无法复制函数、Symbol、undefined等。
javascriptCopy Codeconst obj = { a: 1, b: { c: 2 } };
const deepCopyObj = JSON.parse(JSON.stringify(obj));
deepCopyObj.b.c = 3; // 不影响 obj.b.c
4.5 递归实现深拷贝
递归函数可以实现更灵活的深拷贝,处理更多类型的数据。
javascriptCopy Codefunction deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const newObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
const original = { a: 1, b: { c: 2 } };
const copied = deepClone(original);
copied.b.c = 3; // 不影响 original.b.c
注意事项
在进行深浅拷贝时,需要注意以下几点:
- 性能问题:深拷贝通常比浅拷贝更耗时,尤其是在对象较大或嵌套层级较多时。
- 数据类型限制:使用
JSON.parse(JSON.stringify())时,无法处理函数、Date对象、RegExp对象等特殊数据类型。 - 循环引用:如果对象存在循环引用,使用
JSON.parse(JSON.stringify())会抛出错误,而递归方法需要额外处理。
总结
通过本文的深入讲解,相信你对JavaScript中的深拷贝和浅拷贝有了全面的理解。不同的拷贝方式适用于不同的场景,选择合适的方法可以提升代码的性能和稳定性。掌握这些概念和技巧,对于日常开发工作将大有裨益。
希望这篇文章能够帮助你在实际项目中更好地应用深拷贝和浅拷贝!如果有任何问题或想法,欢迎在评论区留言讨论。