美文网首页让前端飞
谈深拷贝实现思路

谈深拷贝实现思路

作者: EdmundChen | 来源:发表于2019-02-15 16:44 被阅读0次

    深拷贝和浅拷贝都是针对的引用类型,对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据
    那么如何切断a和b之间的关系呢,可以拷贝一份a的数据,根据拷贝的层级不同可以分为浅拷贝深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝

    深拷贝

    深拷贝的问题其实可以分解成两个问题,浅拷贝+递归
    假设要写个function实现深拷贝需要注意的问题

      1. 对参数的检验
      1. 判断是否对象的逻辑要严谨
      1. 考虑数组的兼容
      1. 对Symbol,function等数据类型copy问题
      1. 递归爆栈问题
      1. 循环引用问题

    一、递归实现

    export function clone(x) {
        if (/*如果不是引用类型直接返回*/) {
          return x;
        }
        const t = checkType(x);//检测试array还是object
    
        let res;
    
        if (t === 'array') {
            res = [];
            for (let i = 0; i < x.length; i++) {
                // 避免一层死循环 a.b = a
                res[i] = x[i] === x ? res: clone(x[i]);
            }
        } else if (t === 'object') {
            res = {};
            for(let key in x) {
                if (hasOwnProp(x, key)) {
                    // 避免一层死循环 a.b = a
                    res[key] = x[key] === x ? res : clone(x[key]);
                }
            }
        }
    
        return res;
    }
    

    此方法递归层数过大之后会有爆栈问题,// Maximum call stack size exceeded

    二、JSON 方法实现

    // 通过JSON深拷贝
    export function cloneJSON(x, errOrDef = true) {
        if (/*如果不是引用类型直接返回*/) {
          return x;
        }
    
        try {
            return JSON.parse(JSON.stringify(x));
        } catch(e) {
            if (errOrDef === true) {
                throw e;
            } else {
                console.error('cloneJSON error: ' + e.message);
                return errOrDef;
            }
        }
    }
    
    1. 也有爆栈问题//// Maximum call stack size exceeded
    2. 循环引用情况直接报错// // Uncaught TypeError: Converting circular structure to JSON
    3. undefined、任意的function以及 symbol值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)
    4. symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

    三、递归爆栈(消除递归,使用循环)

    // 循环
    export function cloneLoop(x) {
        const t = checkType(x);
    
        let root = x;
    
        if (t === 'array') {
            root = [];
        } else if (t === 'object') {
            root = {};
        }
    
        // 循环数组
        const loopList = [
            {
                parent: root,
                key: undefined,
                data: x,
            }
        ];
    
        while(loopList.length) {
            // 深度优先
            const node = loopList.pop();
            const parent = node.parent;
            const key = node.key;
            const data = node.data;
            const tt = type(data);
    
            // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
            let res = parent;
            if (typeof key !== 'undefined') {
                res = parent[key] = tt === 'array' ? [] : {};
            }
    
            if (tt === 'array') {
                for (let i = 0; i < data.length; i++) {
                    // 避免一层死循环 a.b = a
                    if (data[i] === data) {
                        res[i] = res;
                    } else if (isClone(data[i])) {
                        // 下一次循环
                        loopList.push({
                            parent: res,
                            key: i,
                            data: data[i],
                        });
                    } else {
                        res[i] = data[i];
                    }
                }
            } else if (tt === 'object'){
                for(let k in data) {
                    if (hasOwnProp(data, k)) {
                        // 避免一层死循环 a.b = a
                        if (data[k] === data) {
                            res[k] = res;
                        } else if (isClone(data[k])) {
                            // 下一次循环
                            loopList.push({
                                parent: res,
                                key: k,
                                data: data[k],
                            });
                        } else {
                            res[k] = data[k];
                        }
                    }
                }
            }
        }
    
        return root;
    }
    

    1.无爆栈问题
    2.依然无法解决循环引用问题

    四、copy缓存对象(解决循环引用问题)

    const UNIQUE_KEY = 'bee' + (new Date).getTime();
    
    // weakmap:处理对象关联引用
    class SimpleWeakmap {
      constructor() {
        this.cacheArray = [];
      }
    
      set(key, value) {
        this.cacheArray.push(key);
        key[UNIQUE_KEY] = value;
      }
    
      get(key) {
        return key[UNIQUE_KEY];
      }
    
      clear() {
        for (let i = 0; i < this.cacheArray.length; i++) {
            let key = this.cacheArray[i];
            delete key[UNIQUE_KEY];
        }
        this.cacheArray.length = 0;
      }
    }
    
    function getWeakMap(){
        let result;
        if(typeof WeakMap !== 'undefined' && type(WeakMap)== 'function'){
            result = new WeakMap();
            if(type(result) == 'weakmap'){
                return result;
            }
        }
        result = new SimpleWeakmap();
    
        return result;
    }
    
    export function cloneForce(x) {
        const uniqueData = getWeakMap();
    
        const t = type(x);
    
        let root = x;
    
        if (t === 'array') {
            root = [];
        } else if (t === 'object') {
            root = {};
        }
    
        // 循环数组
        const loopList = [
            {
                parent: root,
                key: undefined,
                data: x,
            }
        ];
    
        while(loopList.length) {
            // 深度优先
            const node = loopList.pop();
            const parent = node.parent;
            const key = node.key;
            const source = node.data;
            const tt = type(source);
    
            // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
            let target = parent;
            if (typeof key !== 'undefined') {
                target = parent[key] = tt === 'array' ? [] : {};
            }
    
            // 复杂数据需要缓存操作
            if (isClone(source)) {
                // 命中缓存,直接返回缓存数据
                let uniqueTarget = uniqueData.get(source);
                if (uniqueTarget) {
                    parent[key] = uniqueTarget;
                    continue; // 中断本次循环
                }
    
                // 未命中缓存,保存到缓存
                uniqueData.set(source, target);
            }
    
            if (tt === 'array') {
                for (let i = 0; i < source.length; i++) {
                    if (isClone(source[i])) {
                        // 下一次循环
                        loopList.push({
                            parent: target,
                            key: i,
                            data: source[i],
                        });
                    } else {
                        target[i] = source[i];
                    }
                }
            } else if (tt === 'object'){
                for(let k in source) {
                    if (hasOwnProp(source, k)) {
                        if(k === UNIQUE_KEY) continue;
                        if (isClone(source[k])) {
                            // 下一次循环
                            loopList.push({
                                parent: target,
                                key: k,
                                data: source[k],
                            });
                        } else {
                            target[k] = source[k];
                        }
                    }
                }
            }
        }
        
    
        uniqueData.clear && uniqueData.clear();
        
        return root;
    }
    

    参考文献: https://segmentfault.com/a/1190000016672263

    相关文章

      网友评论

        本文标题:谈深拷贝实现思路

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