手写递归深克隆

74 阅读3分钟

先讲一下浅克隆和深克隆的区别 浅克隆和深克隆的区分在于,引用类型的值是是复制了某个对象的指针,还是复制了对象本身

浅克隆方法:

  • Object.assign() [第一层深拷贝,第二层以上都是浅拷贝]
  • ...(扩展运算符)[Symbol.iterator]

深克隆

  • JSON.parse 和 JSON.stringify
  • lodash
  • 手写一个

先自己手写一个克隆

function cloneDeep(obj) {
    if (typeof obj !== 'object' || obj === null) return obj;
    const result = {};
    Object.entries(obj).forEach(([key, value]) => {
        result[key] = cloneDeep(value)
    })
    return result;
}

。。。我怎么一下就写出来了 仔细一看,我漏了两个地方 1.数组 2.无限循环

数组,判断一下,当前是数组还是对象 无限循环,增加一个hash记录之前的对象的地址,如果引用相同地址,就返回新的对象

function cloneDeep(obj, Hash = new WeakMap()) {
    if (typeof obj !== 'object' || obj === null) return obj;
    const result = Array.isArray(obj) ? [] : {};
    if (Hash.has(obj)) return Hash.get(obj)
    Object.entries(obj).forEach(([key, value]) => {
        result[key] = cloneDeep(value,Hash)
    })
    Hash.set(obj, result);
    return result;
}

测试一下

var a = {
    x1: '1',
    x2: 1,
    x3: false,
    x4: undefined,
    x5: null,
    x6: NaN,
    x7: Symbol(),
    x8: function() {},
    x9: [],
    x10: {},
    x11: [[]],
    x12: {x1:{}},
    x13: {x1:a}
}
var b = cloneDeep(a);
// 先测试第一层
Object.entries(a).forEach(([key,value]) => {
    console.log(key, b[key] === value)
})

x1 true
x2 true
x3 true
x4 true
x5 true
x6 false
x7 true
x8 true
x9 false
x10 false
x11 false
x12 false
x13 false

// 测试第二层
a.x11[0] === b.x11[0]  // false
a.x12['x1'] === b.x12['x1'] // false
a === b.x13['x1'] // false

暂时测试通过。

看看别人怎么写的递归深克隆

function deepClone(origin, target, hash = new WeakMap()) {
    //origin:要被拷贝的对象
    // 需要完善,克隆的结果和之前保持相同的所属类
    var target = target || {};

    // 处理特殊情况
    if (origin == null) return origin;  //null 和 undefined 都不用处理
    if (origin instanceof Date) return new Date(origin);
    if (origin instanceof RegExp) return new RegExp(origin);
    if (typeof origin !== 'object') return origin;  // 普通常量直接返回

    //  防止对象中的循环引用爆栈,把拷贝过的对象直接返还即可
    if (hash.has(origin)) return hash.get(origin);
    hash.set(origin, target)  // 制作一个映射表

    // 拿出所有属性,包括可枚举的和不可枚举的,但不能拿到symbol类型
    var props = Object.getOwnPropertyNames(origin);
    props.forEach((prop, index) => {
        if (origin.hasOwnProperty(prop)) {
            if (typeof (origin[prop]) === "object") {
                if (Object.prototype.toString.call(origin[prop]) == "[object Array]") {
                    //数组                            
                    target[prop] = [];
                    deepClone(origin[prop], target[prop], hash);
                } else if (Object.prototype.toString.call(origin[prop]) == "[object Object]") {
                    //普通对象 
                    target[prop] = {};

                    deepClone(origin[prop], target[prop], hash);
                } else if (origin[prop] instanceof Date) {
                    // 处理日期对象
                    target[prop] = new Date(origin[prop])
                } else if (origin[prop] instanceof RegExp) {
                    // 处理正则对象
                    target[prop] = new RegExp(origin[prop])
                } else {
                    //null                                                
                    target[prop] = null;
                }
            } else if (typeof (origin[prop]) === "function") {
                var _copyFn = function (fn) {
                    var result = new Function("return " + fn)();
                    for (var i in fn) {
                        deepClone[fn[i], result[i], hash]
                    }
                    return result
                }
                target[prop] = _copyFn(origin[prop]);
            } else {
                //除了object、function,剩下都是直接赋值的原始值
                target[prop] = origin[prop];
            }
        }
    });

    // 单独处理symbol            
    var symKeys = Object.getOwnPropertySymbols(origin);
    if (symKeys.length) {
        symKeys.forEach(symKey => {
            target[symKey] = origin[symKey];
        });
    }
    return target;
}

比较之下代码问题

  1. Date,Function等也要克隆
  2. Symbol类型的没有克隆

优化一下

function cloneDeep(origin, hash = new WeakMap()) {
    if (origin == null) return origin;
    if (typeof origin === 'symbol') return origin;
    if (origin instanceof Date) return new Date(origin);
    if (origin instanceof RegExp) return new RegExp(origin); 
    if (typeof origin === 'function') {
        if (/^function/.test(origin.toString()) || /^\(\)/.test(origin.toString())) {
            return new Function('return ' + origin.toString())()
        }

        return new Function('return function ' + origin.toString())()
    }
    if (origin instanceof RegExp) return new RegExp(origin);
    if (typeof origin !== 'object') return origin;

    if (hash.has(origin)) return hash.get(origin);
    
    const result = Array.isArray(origin) ? [] : {};
    const AllKeys = Object.getOwnPropertyNames(origin)
    .concat(Object.getOwnPropertySymbols(origin));
    
    for (let key in  AllKeys) {
        result[key] = cloneDeep(origin[key], hash);
    }
    hash.set(origin, result);
    return result;
}