浅析深拷贝、浅拷贝

92 阅读2分钟

深拷贝与浅拷贝

深拷贝就是将原对象的值都复制出来,在内存中重新开辟一个地址,去存放这些对象的值,且修改新对象的值,不会对之前对象的值造成修改。

如果是深拷贝拷贝的是值的引用,那么浅拷贝就是拷贝的是地址的引用,改变原对象跟新对象中的任何一个,都会对数据造成变更。

常见的浅拷贝

  1. object.assign
  2. rest修饰符(...)
  3. Array.prototype.concat
  4. Array.prototype.slice

深拷贝的实现

JSON.parse(JSON.stringify(obj))

缺点:

  • 无法copy方法
  • 无法解决循环引用的问题
  • 无法处理Date regExp

实现一个深拷贝

function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

那么问题来了,这个例子可以解决下列几个问题么

  • 如果拷贝的是对象是Date这样的对象,有预期的效果吗?
  • 如果是循环引用呢?

深拷贝的优化

    function clone(target, map = new WeakMap()) {
        if (typeof target === 'object') {
            const isArray = Array.isArray(target);
            let cloneTarget = isArray ? [] : {};

            if (map.get(target)) {
                return map.get(target);
            }
            map.set(target, cloneTarget);

            const keys = isArray ? undefined : Object.keys(target);
            forEach(keys || target, (value, key) => {
                if (keys) {
                    key = value;
                }
                cloneTarget[key] = clone2(target[key], map);
            });

            return cloneTarget;
        } else {
            return target;
        }
    }

这样就解决了循环引用的问题,但是还是无法解决一些无法遍历的对象,如Date,Error等,所以我们要对部分特殊的对象做相同的处理

    /** 完整版本 */
function deepClonea(val, map = new WeakMap()) {
    let type = getType(val); //当是引用类型的时候先拿到其确定的类型
    if (isObj(val)) {
        switch (type) {
            case 'date':                   //日期类型重新new一次传入之前的值,date实例化本身结果不变
                return new Date(val);
                break;
            case 'regexp':                 //正则类型直接new一个新的正则传入source和flags即可
                return new RegExp(val.source, val.flags);
                break;
            case 'function':               //如果是函数类型就直接通过function包裹返回一个新的函数,并且改变this指向
                return new RegExp(val.source, val.flags);
                break;
            default:
                let cloneVal = Array.isArray(val) ? [] : {};
                if (map.has(val)) return map.get(val)
                map.set(val, cloneVal)
                for (let key in val) {
                    if (val.hasOwnProperty(key)) { //判断是不是自身的key
                        cloneVal[key] = deepClone(val[key]), map;//每一项就算是基本类型也需要走deepclone方法进行拷贝
                    }
                }
                return cloneVal;
        }
    } else {
        return val;     //当是基本数据类型的时候直接返回
    }
}
function isObj(val) {   //判断是否是引用类型
    return (typeof val == 'object' || typeof val == 'function') && val != null
}
function getType(data) { //获取类型
    var s = Object.prototype.toString.call(data);
    return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
// /** 测试 */
var a = {}
a.a = a
var b = deepClonea(a)
console.log(b)