JS 递归深拷贝
· 阅读需 4 分钟
思路
从最基本的拷贝开始,一步一步处理更复杂的对象、类型。
拷贝对象字面量
先考虑基本类型和层层对象的情况,直接 for-in 遍历 + 递归拷贝
function deepClone(obj) {
// 基本类型直接返回
if (typeof obj !== 'object') return obj;
const newObj = {};
for (let key in obj) {
// 只复制自身的对象
if (Object.hasOwn(obj, key)) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
解决循环引用问题
可以用 Map 或 WeakMap 解决,对复杂数据、对象字面量进行深拷贝前保存其引用,如果下次又出现了该值,就不再进行拷贝,直接返回保存的值。
function deepClone(obj, hash = new WeakMap()) {
if (typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
const newObj = {};
hash.set(obj, newObj);
for (let key in obj) {
if (Object.hasOwn(obj, key)) {
newObj[key] = deepClone(obj[key], hash);
}
}
return newObj;
}
现在已经支持复制这样的对象 o
了:
const o = {
name: 'Lee',
attrs: {
degree: 'BA',
year: 2019
}
};
const b = {};
o.reference = b;
b.reference = o;
处理构造出的且可直接赋值的对象
一些构造出来的对象,例如 Array 和 Object 的实例,可能是 [1,2,3]
或者 { a:1, b:2}
,他们支持 for-in 遍历,而且可以 obj[key] = value
这样赋值,就拿他们的构造函数 new 出一个新对象,然后放在 for-in 遍历里面拷贝每一个值即可。
function deepClone(obj, hash = new WeakMap()) {
if (typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
// 对象本身没 constructor 属性,但可以访问到,因为会沿着原型链往上找
// 如果是 {} ,就相当于 new Object();如果是数组,就相当于 new Array()
let newObj = new obj.constructor;
hash.set(obj, newObj);
for (let key in obj) {
if (Object.hasOwn(obj, key)) {
newObj[key] = deepClone(obj[key], hash);
}
}
return newObj;
}