什么是深克隆
深克隆是指在复制对象时,不仅复制对象本身,还递归复制对象的所有子对象,且原始对象和克隆对象之间完全独立,修改其中一个不会影响另一个。
为什么需要深克隆
js中对象是引用类型,其内容存在堆中,栈中只存储了指针(可理解为Map数据格式的key),当使用 = 将对象赋值给另一个变量时其实只是把指针赋值给了变量(即浅拷贝)。 在实际开发过程中,有时需要对数据进行处理但又不想影响到原始数据,这时针对一些复杂的数据格式(数组、对象等)就需要使用深克隆来实现。
常见的深克隆方法
使用 JSON.parse 和 JSON.stringify
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
console.log(cloned);
缺点:无法处理函数、循环引用、undefined和某些特殊对象(Date、RefExp等)
递归函数实现
function deepClone(obj) {
// 存储已拷贝的对象,避免循环引用时的死循环
const map = new Map();
function clone(originObj) {
if (originObj == null) {
return originObj;
}
if (typeof originObj !== 'object') {
return originObj;
}
if (map.has(originObj)) {
return map.get(originObj);
}
const type = typeDetection(originObj);
switch (type) {
case 'array': {
const result = [];
map.set(originObj, result);
originObj.forEach(item => {
result.push(clone(item));
});
return result;
}
case 'object': {
const keys = Object.keys(originObj);
// 获取对象所有属性的描述符,可获取属性是否只读等属性
const desc = Object.getOwnPropertyDescriptors(originObj);
// 创建新对象并继承原对象的原型链
const result = Object.create(Object.getPrototypeOf(originObj), desc);
map.set(originObj, result);
keys.forEach(key => {
result[key] = clone(originObj[key]);
})
return result;
}
case 'map': {
const result = new Map();
map.set(originObj, result);
originObj.forEach((value, key) => {
result.set(clone(key), clone(value));
})
return result;
}
case 'set': {
const result = new Set();
map.set(originObj, result);
originObj.forEach(item => {
result.add(clone(item));
})
return result;
}
case 'function': {
return new Function(`return ${originObj.toString()}`)();
}
case 'date': {
return new Date(originObj.getTime());
}
case 'regexp': {
return new RegExp(originObj.source, originObj.flags);
}
case 'error': {
return new Error(originObj.message);
}
}
}
// 类型检测
function typeDetection(obj) {
if (obj == null) {
return obj + '';
}
if (typeof obj === 'object') {
const typeMapping = {};
['Array', 'Object', 'Map', 'Set', 'Function', 'Date', 'RegExp', 'Error'].forEach(element => {
typeMapping[`[object ${element}]`] = element.toLowerCase();
});
const type = Object.prototype.toString.call(obj);
return typeMapping[type];
}
return typeof obj;
}
return clone(obj);
}