前言:
1.浅拷贝(Shallow Copy):浅拷贝只复制对象的第一层属性。对于原始类型(如字符串、数字、布尔值),浅拷贝会创建其副本,而对于对象或数组这样的复杂类型,浅拷贝只复制引用,而不复制其实际内容。这意味着被拷贝对象的子对象或数组仍然和原对象共享同一个引用。
简单点来说就是:基于原对象,拷贝得到一个新的对象,原对象中内容的修改会影响新对象。
2.深拷贝(Deep Copy):深拷贝会递归地复制对象及其所有子对象,从而创建一个与原对象完全独立的副本。对于复杂的数据结构,深拷贝会确保新对象与原对象之间没有任何引用关系。因此,修改深拷贝后的对象不会影响原对象。
简单点来说:基于原对象,拷贝得到一个新的对象,原对象中内容的修改不会影响新对象
直接上最容易理解的代码
一、浅拷贝实现
1.Object.create(obj)
其中:修改newObj
中的值originalObj 的值也发生改变。
let originalObj = {
a: 1,
b: { c: 2 }
};
// 使用 Object.create() 创建新对象
let newObj = Object.create(originalObj);
console.log(newObj.a); // 1 (继承自 originalObj)
console.log(newObj.b.c); // 2 (继承自 originalObj)
// 修改 newObj 的 b 属性(引用类型),会影响 originalObj
newObj.b.c = 100;
console.log(newObj.b.c); // 100 (修改了 newObj 也修改了 originalObj 中 b 的内容)
console.log(originalObj.b.c); // 100 (原型对象也被影响)
// 添加新属性到 newObj
newObj.a = 500;
console.log(originalObj.a); // 1 (原型对象中的原始类型属性不会被影响)
console.log(newObj.a); // 500 (新属性直接添加到了 newObj 自身)
注意:虽然 Object.create()
并不直接用于浅拷贝,但通过共享引用的特性,我们可以看到修改对象中的引用类型(如嵌套对象)会导致原型对象也被影响,从而体现出类似浅拷贝的行为。
2.Object.assign({}, obj)
Object.assign()
的浅拷贝特性表现在:如果对象内部包含嵌套的引用类型(如对象或数组),这些嵌套对象只是复制了引用地址,而不会创建独立的副本。因此,修改浅拷贝中的嵌套对象属性时,原对象也会随之改变。
let originalObj = {
a: 1,
b: { c: 2 }
};
// 使用 Object.assign() 实现浅拷贝
let shallowCopy = Object.assign({}, originalObj);
console.log(shallowCopy); // 输出: { a: 1, b: { c: 2 } }
// 修改浅拷贝后的对象的原始类型属性
shallowCopy.a = 100;
console.log(shallowCopy.a); // 输出: 100
console.log(originalObj.a); // 输出: 1 (原对象的 a 未受影响)
// 修改浅拷贝后的对象的引用类型属性
shallowCopy.b.c = 200;
console.log(shallowCopy.b.c); // 输出: 200
console.log(originalObj.b.c); // 输出: 200 (原对象的 b.c 被影响)
// 检查引用是否相同
console.log(shallowCopy.b === originalObj.b); // true,b 引用相同
3.[ ].concat(arr)
[].concat(arr)
是一种实现浅拷贝的方法,通常用于复制数组。它会创建一个新数组,并将原数组的第一层元素复制到新数组中。如果原数组中包含引用类型(如对象或子数组),则这些引用类型元素的引用地址会被复制,而不会复制它们的实际内容。
let originalArray = [1, 2, { a: 10, b: 20 }];
// 使用 [].concat() 进行浅拷贝
let shallowCopy = [].concat(originalArray);
// 浅拷贝后的数组和原数组内容相同
console.log(shallowCopy); // 输出: [1, 2, { a: 10, b: 20 }]
// 修改浅拷贝后的数组中的原始类型元素
shallowCopy[0] = 100;
console.log(shallowCopy); // 输出: [100, 2, { a: 10, b: 20 }]
console.log(originalArray); // 输出: [1, 2, { a: 10, b: 20 }] (原数组未受影响)
// 修改浅拷贝后的数组中的引用类型元素
shallowCopy[2].a = 200;
console.log(shallowCopy); // 输出: [100, 2, { a: 200, b: 20 }]
console.log(originalArray); // 输出: [1, 2, { a: 200, b: 20 }] (原数组的对象元素也被修改)
// 检查引用是否相同
console.log(shallowCopy[2] === originalArray[2]); // 输出: true (引用类型元素共享同一引用)
4.[...arr]展开运算符实现
它只复制原数组中的第一层元素,对于原始类型(如数字、字符串)会创建一个新的副本,但对于引用类型(如对象、子数组),只复制引用地址,不复制内容。
let originalArray = [1, 2, { a: 10, b: 20 }];
// 使用展开运算符 [...] 进行浅拷贝
let shallowCopy = [...originalArray];
console.log("原数组:", originalArray); // 输出: [1, 2, { a: 10, b: 20 }]
console.log("浅拷贝数组:", shallowCopy); // 输出: [1, 2, { a: 10, b: 20 }]
// 修改浅拷贝数组的原始类型元素
shallowCopy[0] = 100;
console.log("修改后的浅拷贝数组:", shallowCopy); // 输出: [100, 2, { a: 10, b: 20 }]
console.log("修改后的原数组:", originalArray); // 输出: [1, 2, { a: 10, b: 20 }] (原数组未受影响)
// 修改浅拷贝数组的引用类型元素
shallowCopy[2].a = 200;
console.log("修改后的浅拷贝数组:", shallowCopy); // 输出: [100, 2, { a: 200, b: 20 }]
console.log("修改后的原数组:", originalArray); // 输出: [1, 2, { a: 200, b: 20 }] (原数组被影响)
// 检查引用是否相同
console.log(shallowCopy[2] === originalArray[2]); // true,共享同一对象引用
5.arr.slice()
原理同上
// 原始数组
const arr = [1, 2, 3,{a:"小小"}];
// 浅拷贝得到数组
const shallowCopy = arr.slice(0);
// 修改原数组的值
arr[1] = 20; // 不会改变新对象
arr[3].a = '小红'; // 会改变新对象
// 输出结果
console.log('Shallow Copy Array:', shallowCopy); // 输出Shallow Copy Array: [ 1, 2, 3, { a: '小红' } ]
6.arr.reverse().reverse()
实际上是对原数组进行了两次反转操作,结果是将数组的顺序恢复到最初的顺序。
let originalArray = [1, 2, 3, 4, 5];
let sameArray = originalArray.reverse().reverse();
console.log(sameArray === originalArray); // 输出: true,表示它们是同一个数组的引用
二、深拷贝
1.JSON.parse(JSON.stringify(obj))
let originalObj = {
a: 1,
b: {
c: 2
}
};
// 使用 JSON.parse(JSON.stringify()) 进行深拷贝
let deepCopy = JSON.parse(JSON.stringify(originalObj));
// 修改深拷贝后的对象
deepCopy.b.c = 100;
console.log("原对象:", originalObj); // 输出: { a: 1, b: { c: 2 } }
console.log("深拷贝对象:", deepCopy); // 输出: { a: 1, b: { c: 100 } }
结论:JSON.parse(JSON.stringify(obj))
是一种常用的深拷贝方法。它通过将对象转换为 JSON 字符串,然后再解析回 JavaScript 对象,从而创建一个完全独立的对象副本。与浅拷贝方法不同,它可以深度复制嵌套的引用类型数据(如对象或数组),从而确保修改副本时不会影响原始对象。
2.递归拷贝(手动实现)
function deepClone(obj) {
if (typeof obj !== "object" || obj === null) return obj; // 处理原始类型或 null
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]); // 递归拷贝每个属性
}
}
return copy;
}
let obj = { a: 1, b: { c: 2 }, d: [3, 4] };
let deepCopy = deepClone(obj);
deepCopy.b.c = 100;
deepCopy.d[0] = 300;
console.log(obj.b.c); // 输出: 2
console.log(obj.d[0]); // 输出: 3
console.log(deepCopy.b.c); // 输出: 100
console.log(deepCopy.d[0]); // 输出: 300
3.structuredClone()
(现代 API)
JS官方推出的一种深拷贝方法structuredClone()
let obj = { a: 1, b: { c: 2 }, d: [3, 4], e: new Date() };
let deepCopy = structuredClone(obj);
deepCopy.b.c = 100;
deepCopy.d[0] = 300;
console.log(obj.b.c); // 输出: 2
console.log(obj.d[0]); // 输出: 3
console.log(deepCopy.b.c); // 输出: 100
console.log(deepCopy.d[0]); // 输出: 300
优点:
- 原生支持,性能较好。
- 支持大多数数据类型,包括循环引用。
缺点:
- 需要现代浏览器(支持性不够广泛)。
- 某些老旧浏览器可能不支持。
结论
总结一下就是:
浅拷贝方法:
Object.create(x)
Object.assign({}, a)
[].concat(arr)
[...arr]解构数组
arr.slice()
arr.toReversed().reverse()
深拷贝方法:
JSON.parse(JSON.stringify(obj))
通过递归去处理数据
structuredClone();
希望大家能通过本文对深浅拷贝有进一步的学习和认知。 欢迎多多评论和指导