深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是编程中(前端我们讨论js)常见的概念,尤其是在处理数据结构如数组和对象时。它们描述了复制数据时的不同行为和结果。
浅拷贝(Shallow Copy)
浅拷贝是指创建一个新对象,这个对象有着原始对象属性值的一份拷贝。如果属性是基本类型,拷贝的是值;如果属性是引用类型(如数组或对象),拷贝的是引用,而不是引用的对象本身。
原理
-
对于基本数据类型,直接复制值。
-
对于引用数据类型,复制引用的地址,而不是引用的对象。
示例
const original = { a: 1, b: [2, 3] };
const shallowCopy = { ...original };
shallowCopy.b.push(4);
console.log(original.b); // [2, 3, 4],数组被修改了
shallowCopy.a = 5;
console.log(original.a); // 1,a属性没有被修改
在这个例子中,shallowCopy
对 b
数组的修改影响了 original
,因为数组是引用类型,只复制了引用。但对 a
属性的修改不影响 original
,因为 a
是基本类型,复制了值。
深拷贝(Deep Copy)
深拷贝是指递归地复制对象,包括对象的所有属性,以及属性值所引用的对象,直到所有的层级。
原理
-
对于基本数据类型,直接复制值。
-
对于引用数据类型,递归复制引用的对象,直到没有引用。
示例
const original = { a: 1, b: [2, 3] };
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.push(4);
console.log(original.b); // [2, 3],数组没有被修改
deepCopy.a = 5;
console.log(original.a); // 1,a属性没有被修改
在这个例子中,deepCopy
对 b
数组的修改不影响 original
,因为数组和它的元素都被递归复制了。
区别
-
复制的深度:
-
浅拷贝只复制一层,如果属性是引用类型,复制的是引用。
-
深拷贝递归复制所有层级,复制的是引用的对象本身。
-
-
性能:
-
浅拷贝通常性能更好,因为它只复制一层。
-
深拷贝可能性能较差,因为它需要递归复制所有层级。
-
-
使用场景:
-
当对象没有多层嵌套或只有一层嵌套时,可以使用浅拷贝。
-
当对象有多层嵌套,且需要完全独立的副本时,使用深拷贝。
-
-
实现方式:
-
浅拷贝可以通过展开运算符(
...
)、Object.assign()
或数组的slice()
方法实现。 -
深拷贝可以通过
JSON.parse(JSON.stringify())
、递归函数或使用第三方库(如 Lodash 的_.cloneDeep()
)实现。
-
手撕实现
在JavaScript中,实现深拷贝和浅拷贝可以通过多种方法完成。下面我将提供一些手撕代码示例,展示如何实现这两种拷贝。
1. 浅拷贝实现:
思路:
浅拷贝相对简单,可以使用展开运算符(...
)、Object.assign()
方法或者Array.prototype.slice()
方法等来实现。
手撕代码:
// 使用展开运算符实现浅拷贝
const shallowCopy = (obj) => ({ ...obj });
// 使用Object.assign实现浅拷贝
const shallowCopyAssign = (target, ...sources) => Object.assign(target, ...sources);
// 使用slice方法实现数组的浅拷贝
const shallowCopyArray = (arr) => arr.slice();
2. 深拷贝实现:
思路:
深拷贝的实现较为复杂,需要递归地复制对象的所有层级。以下是一个简单的深拷贝实现,它使用递归函数来处理对象和数组的拷贝,同时考虑到了一些特殊情况,如日期、正则表达式和循环引用。
手撕代码:
const deepCopy = (obj, hash = new WeakMap()) => {
if (obj === null) return null; // null值处理
if (obj instanceof Date) return new Date(obj); // 日期对象处理
if (obj instanceof RegExp) return new RegExp(obj); // 正则对象处理
if (typeof obj !== 'object') return obj; // 基本数据类型直接返回
if (hash.has(obj)) return hash.get(obj); // 循环引用处理
let cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepCopy(obj[key], hash); // 递归复制
}
}
return cloneObj;
};
这个deepCopy
函数可以处理对象、数组、日期、正则表达式以及循环引用。它通过一个WeakMap
来存储已经拷贝过的对象,以避免循环引用导致无限递归。
3. 使用示例
const original = {
a: 1,
b: [2, 3],
c: {
d: 4,
e: [5, 6]
}
};
const shallow = shallowCopy(original);
const deep = deepCopy(original);
console.log(shallow.b === original.b); // true,数组是引用类型
console.log(deep.b === original.b); // false,数组是独立副本
deep.c.e.push(7);
console.log(original.c.e); // [5, 6],不影响原始对象
通过这些示例,你可以看到浅拷贝和深拷贝在处理对象时的不同行为。在实际应用中,根据具体需求选择合适的拷贝方法。理解深拷贝和浅拷贝的原理和区别对于正确处理数据结构和避免潜在的bug非常重要。