深克隆

99 阅读1分钟

什么是深克隆

深克隆是指在复制对象时,不仅复制对象本身,还递归复制对象的所有子对象,且原始对象和克隆对象之间完全独立,修改其中一个不会影响另一个。

为什么需要深克隆

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);
}