美文网首页
js递归实现深拷贝 代码优化(解决循环引用)

js递归实现深拷贝 代码优化(解决循环引用)

作者: mudssky | 来源:发表于2021-03-23 15:59 被阅读0次

    首先回顾一下之前实现的深拷贝的代码:

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

    01.循环引用问题

    我们看下面这个例子

    testObj = {
        num: 123,
    }
    testObj.target = testObj
    
    let newObj = deepClone(testObj)
    

    这段代码运行将会报错,因为循环引用造成了递归的栈溢出

    C:\Projects\test\deepClone.js:18
                cloneTarget[key] = deepClone(target[key]);
                                   ^
    
    RangeError: Maximum call stack size exceeded
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
        at deepClone (C:\Projects\test\deepClone.js:18:32)
    

    为了解决循环引用问题,我们需要一个存储容器存放当前对象和拷贝对象的对应关系(适合用key-value的数据结构进行存储,也就是map),当进行拷贝当前对象的时候,我们先查找存储容器是否已经拷贝过当前对象,如果已经拷贝过,那么直接把返回,没有的话则是继续拷贝。

    这样碰到循环引用的对象的时候,可以通过存储的对应关系进行复现。

    这次经过改进消除循环引用的深拷贝如下:

    function deepClone(target) {
        const map = new Map()
        function clone (target) {
            if (isObject(target)) {
                let cloneTarget = isArray(target) ? [] : {};
                if (map.get(target)) {
                    return map.get(target)
                }
                map.set(target,cloneTarget)
                for (const key in target) {
                    cloneTarget[key] = clone(target[key]);
                }
                return cloneTarget;
            } else {
                return target;
            }
        }
        return clone(target)
    };
    

    性能优化

    1. 据说for in的执行效率很低(好处是,可以兼容数组和对象的遍历,并且会遍历原型链上的东西),对比普通for循环也是while循环的执行效率会更高,所以我们把遍历数组的部分改成用while。object类型的可以使用object.keys获取所有的key的列表,这样就也可以用while循环的方式遍历提升效率
    2. 垃圾回收的效率,return之前调用map.clear也许效率会更高(没有实际测试过)

    02.其他数据类型

    之前我们只考虑了最基本的object和array

    合理判断引用类型

    之前我们判断引用类型没有考虑到null和function的情况

    修改后如下

    function isObject(target) {
        const targetType = typeof target
        return targetType!==null&&(targetType==='object'||targetType==='function')
    }
    

    获取数据类型

    引用类型都有toString方法,我们通过输出的字符串可以判断具体是什么类型

    function getType(target) {
        return Object.prototype.toString.call(target);
    }
    

    下面是一些常用的类型

    const mapTag = '[object Map]';
    const setTag = '[object Set]';
    const arrayTag = '[object Array]';
    const objectTag = '[object Object]';
    
    const boolTag = '[object Boolean]';
    const dateTag = '[object Date]';
    const errorTag = '[object Error]';
    const numberTag = '[object Number]';
    const regexpTag = '[object RegExp]';
    const stringTag = '[object String]';
    const symbolTag = '[object Symbol]';
    

    其中可以分为可以继续遍历的类型和不可以继续遍历的类型,我们对各个类型分别做不同的处理

    。。。

    。。。

    。。。

    (过于繁琐,现在没有研究的欲望)

    关于函数拷贝

    没有实际的应用场景,并且实际上是无法实现完美的,因为你无法拷贝函数中的闭包。。。

    总结,js的坑太多了,以后这种功能还是用lodash吧 https://github.com/lodash/lodash

    人生苦短,还是别在别人挖的坑上浪费时间了,具体等碰到具体的应用场景自己自然会去学习相应的知识。

    相关文章

      网友评论

          本文标题:js递归实现深拷贝 代码优化(解决循环引用)

          本文链接:https://www.haomeiwen.com/subject/jipfhltx.html