在 JavaScript 的世界里,数据复制是一个看似简单却暗藏玄机的操作。你是否曾经遇到过修改一个对象后,另一个对象也莫名其妙地发生了变化?这就是浅拷贝在作祟。而深拷贝,则是解决这一问题的关键。本文将带你深入浅出地理解 JavaScript 中的深拷贝与浅拷贝,并探讨它们的应用场景。
浅拷贝:表面的复制
浅拷贝,顾名思义,只复制对象的第一层属性。对于基本数据类型(如字符串、数字、布尔值),浅拷贝会复制其值;而对于引用数据类型(如对象、数组),浅拷贝只会复制其引用地址,而不是实际的值。这意味着,修改浅拷贝后的对象中的引用类型属性,会影响到原始对象。
JavaScript 中实现浅拷贝的方法有很多,例如:
- 展开运算符 (
...):
const person = { name: "Alice", address: { city: "New York" } };
const shallowCopy = { ...person };
shallowCopy.name = "Bob"; // 修改基本数据类型,不影响原对象
shallowCopy.address.city = "Los Angeles"; // 修改引用数据类型,影响原对象
console.log(person); // { name: "Alice", address: { city: "Los Angeles" } }
Object.assign():
const person = { name: "Alice", address: { city: "New York" } };
const shallowCopy = Object.assign({}, person);
shallowCopy.name = "Bob"; // 修改基本数据类型,不影响原对象
shallowCopy.address.city = "Los Angeles"; // 修改引用数据类型,影响原对象
console.log(person); // { name: "Alice", address: { city: "Los Angeles" } }
- 数组的
slice()方法:
const numbers = [1, 2, [3, 4]];
const shallowCopy = numbers.slice();
shallowCopy[0] = 5; // 修改基本数据类型,不影响原数组
shallowCopy[2][0] = 6; // 修改引用数据类型,影响原数组
console.log(numbers); // [1, 2, [6, 4]]
深拷贝:彻底的复制
深拷贝则不同,它会递归地复制对象的所有层级属性,包括嵌套的对象和数组。深拷贝后的对象与原始对象完全独立,修改其中一个不会影响到另一个。
实现深拷贝的方法也有多种:
-
JSON.parse(JSON.stringify()): 这是最简单的方法,但它无法处理函数、undefined、Symbol等特殊数据类型。
const person = { name: "Alice", address: { city: "New York" } };
const deepCopy = JSON.parse(JSON.stringify(person));
deepCopy.name = "Bob"; // 修改基本数据类型,不影响原对象
deepCopy.address.city = "Los Angeles"; // 修改引用数据类型,不影响原对象
console.log(person); // { name: "Alice", address: { city: "New York" } }
- 递归函数: 手动编写递归函数遍历对象的所有属性并进行复制。
function deepCopy(obj) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
const newObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
newObj[key] = deepCopy(obj[key]);
}
return newObj;
}
const person = { name: "Alice", address: { city: "New York" } };
const deepCopy = deepCopy(person);
deepCopy.name = "Bob"; // 修改基本数据类型,不影响原对象
deepCopy.address.city = "Los Angeles"; // 修改引用数据类型,不影响原对象
console.log(person); // { name: "Alice", address: { city: "New York" } }
-
使用第三方库: 例如 Lodash 库的
_.cloneDeep()方法。
const _ = require("lodash");
const person = { name: "Alice", address: { city: "New York" } };
const deepCopy = _.cloneDeep(person);
deepCopy.name = "Bob"; // 修改基本数据类型,不影响原对象
deepCopy.address.city = "Los Angeles"; // 修改引用数据类型,不影响原对象
console.log(person); // { name: "Alice", address: { city: "New York" } }
选择合适的方式
在实际开发中,选择深拷贝还是浅拷贝取决于你的需求。如果你只需要复制对象的第一层属性,并且不担心嵌套对象的引用问题,那么浅拷贝就足够了。但如果你需要完全独立的对象副本,那么深拷贝是更好的选择。
总结
理解深拷贝和浅拷贝是掌握 JavaScript 数据操作的关键。它们就像是一对双胞胎,看似相似,却有着截然不同的性格。选择合适的方式,才能让你的代码更加优雅和高效。
思考题:
- 你能想到哪些场景需要使用深拷贝?
- 表单数据的备份和恢复
- 状态管理库中状态的更新
- 复杂对象的比较和操作
- 除了文中提到的方法,你还知道哪些实现深拷贝的方式?
- 使用
structuredClone()方法 (现代浏览器支持) - 使用
MessageChannelAPI
- 使用