谈深拷贝实现思路

字数 420阅读 137

深拷贝和浅拷贝都是针对的引用类型,对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据
那么如何切断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

推荐阅读更多精彩内容