深拷贝

345 阅读2分钟

读大神ConardLi写的《如何写出一个惊艳面试官的深拷贝?》自己的记录,原文将深拷贝讲的非常仔细非常全面,推荐收藏学习。


主要概念

浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

深拷贝实现要考虑的几个问题

1. 拷贝对象为原始类型

2. 拷贝对象包含多层

使用递归方法解决。
如果是原始类型,无需继续拷贝,直接返回
如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。

3. 拷贝对象为数组

4. 循环引用

对象的属性间接或直接的引用了自身

const obj = {};
obj.self = obj;
obj === obj.self; // true

循环引用这块有个疑问

let target = {name: 'target'};
target.myself = target;
let copy = deepClone(target); // 深拷贝target对象
copy.name = 'copy';

问:按照深拷贝的概念,copy.myself.name === 'copy' 还是 'target' ?

5. 性能优化

使用while循环优化

6. 其他引用类型function, null, Map, Set

源代码


function deepClone(source, map = new WeakMap()) {
    if (typeof source === 'object' && source !== null){
    
        const type = Object.prototype.toString.call(source);
        
        // 使用原对象的构造方法,可以保留对象原型上的数据
        const Ctor = source.constructor;
        let cloneTarget = new Ctor();

        // 防止循环引用
        if(map.get(source)){
            return source;
        }
        map.set(source, cloneTarget);
        
        // 拷贝Set
        if(type === "[object Set]"){
            source.forEach(value => {
                cloneTarget.add(deepClone(value));
            });
            return cloneTarget;
        }
        
        // 拷贝Map
        if(type === "[object Map]"){
            source.forEach((value, key) => {
                cloneTarget.set(key, deepClone(value));
            });
            return cloneTarget;
        }
        
        const isArray = Array.isArray(source);
        const keys = isArray ? source : Object.keys(source);
        let index = 0;
        while(index < keys.length){
            let key = isArray ? index : keys[index]
            if(typeof cloneTarget[key] === 'object') {
                cloneTarget[key] = deepClone(source[key], map);
            } else {
                cloneTarget[key] = source[key];
            }
            index++;
        }

        return cloneTarget;
    } else if (typeof source === 'function'){
        return cloneFunction(source);
    } else {
        return source;
    }
}

function cloneFunction(func){
    const bodyReg = /(?<={)(.|\n)+(?=})/m;
    const paramReg = /(?<=\().+(?=\)\s+{)/;
    const funcString = func.toString();
    // 通过prototype来区分下箭头函数和普通函数,箭头函数是没有prototype
    if (func.prototype) {
        // 使用正则取出函数体和函数参数,然后使用new Function ([arg1[, arg2[, ...argN]],] functionBody)
        // 构造函数重新构造一个新的函数
        const param = paramReg.exec(funcString);
        const body = bodyReg.exec(funcString);
        if (body) {
            console.log('匹配到函数体:', body[0]);
            if (param) {
                const paramArr = param[0].split(',');
                console.log('匹配到参数:', paramArr);
                return new Function(...paramArr, body[0]);
            } else {
                return new Function(body[0]);
            }
        } else {
            return null;
        }
    } else {
        // 使用eval和函数字符串来重新生成一个箭头函数
        return eval(funcString);
    }
}

// 测试用例
obj = {
    boo: true,
    name: 'sunny',
    id: 1001,
    map: new Map(),
    params: {limit: 1000, query: {name: 'aaa'}},
    getName: function (){
        return this.name
    }
}
obj.myself = obj
copy = deepClone(obj)