推荐答案
// www.javascriptcn.com code example
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (hash.has(obj)) {
return hash.get(obj);
}
const clonedObj = Array.isArray(obj) ? [] : {};
hash.set(obj,clonedObj);
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
clonedObj[key] = deepClone(obj[key],hash);
}
}
return clonedObj;
}本题详细解读
深拷贝概念
深拷贝是指创建一个新的对象,这个新对象与原始对象拥有相同的值,但是它们在内存中占据不同的位置。修改深拷贝后的对象不会影响到原始对象,反之亦然。与浅拷贝不同,深拷贝会递归地复制对象的所有层级,包括嵌套的对象和数组。
循环引用问题
当对象中存在循环引用时,即对象属性指向自身或其祖先对象时,传统的深拷贝方法会陷入无限递归,导致栈溢出。解决循环引用问题的关键在于记录已经拷贝过的对象,并在拷贝过程中优先查找是否已经拷贝过。
实现方法详解
基础类型和 null 处理:
- 如果要拷贝的值是
null或者不是object类型(例如:string,number,boolean,undefined,symbol),直接返回该值。
- 如果要拷贝的值是
循环引用检测:
- 使用
WeakMap来存储已经拷贝过的对象,WeakMap的键是原始对象,值是拷贝后的对象。 - 在拷贝开始时,检查
WeakMap中是否已经存在当前对象,如果存在,则直接返回WeakMap中存储的拷贝对象,避免无限递归。
- 使用
创建新对象:
- 根据原始对象的类型,创建一个新的空对象或空数组,准备存放拷贝后的属性和值。
存储已拷贝对象:
- 将原始对象作为键,拷贝的新对象作为值存入
WeakMap中,方便后续的循环引用检测。
- 将原始对象作为键,拷贝的新对象作为值存入
递归拷贝:
- 遍历原始对象的所有属性,使用
Object.hasOwnProperty.call确保只遍历对象自身的属性,不包含原型链上的属性。 - 对于每个属性值,递归调用
deepClone函数,将当前属性值和已经构建的WeakMap作为参数传入。 - 将递归拷贝返回的结果赋值给新对象的对应属性。
- 遍历原始对象的所有属性,使用
返回拷贝对象:
- 完成所有属性拷贝后,返回新创建的拷贝对象。
为什么使用 WeakMap
WeakMap的键必须是对象,符合我们的拷贝需要。WeakMap的键是弱引用,不会阻止垃圾回收,当原始对象被回收后,对应的WeakMap项也会被自动移除,避免内存泄漏。WeakMap可以高效的查询键是否存在。