深拷贝与浅拷贝的方法

385 阅读2分钟

浅拷贝只能拷贝最外面的一层,更深层次对象级别的只能拷贝引用 深拷贝拷贝多层,每一级别的数据都会被拷贝(会开辟新的空间)

1.slice() concat、Array.from() 实现一维数组的拷贝(浅拷贝)

var arr1 = [1, 2, [3, 4]], arr2 = arr1.slice();
console.log(arr1); //[1, 2, [3, 4]]
console.log(arr2); //[1, 2, [3, 4]]

arr2[2][1] = 5; 
console.log(arr1); //[1, 2, [3, 5]]
console.log(arr2); //[1, 2, [3, 5]]

2.Object.assign()实现一维对象的拷贝(浅拷贝)

var obj1 = {x: 1, y: 2}, obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 1, y: 2}

obj2.x = 2; //修改obj2.x
console.log(obj1) //{x: 1, y: 2}
console.log(obj2) //{x: 2, y: 2}

var obj1 = {
    x: 1, 
    y: {
        m: 1
    }
};
var obj2 = Object.assign({}, obj1);
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}

obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 2}}
console.log(obj2) //{x: 2, y: {m: 2}}

3.JSON.parse(JSON.stringify(obj)) 不能深拷贝含有undefined、function、symbol值的对象(深拷贝)

var obj1 = {
x: 1, 
    y: {
        m: 1
    }
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 1, y: {m: 1}}

obj2.y.m = 2; //修改obj2.y.m
console.log(obj1) //{x: 1, y: {m: 1}}
console.log(obj2) //{x: 2, y: {m: 2}}

var obj1 = {
    x: 1,
    y: undefined,
    z: function add(z1, z2) {
        return z1 + z2
    },
    a: Symbol("foo")
};
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1) //{x: 1, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(JSON.stringify(obj1)); //{"x":1}
console.log(obj2) //{x: 1}

4.递归(JS 提供的自有方法并不能彻底解决Array、Object的深拷贝问题)

function deepCopy(obj) {
    // 创建一个新对象
    let result = {}
    let keys = Object.keys(obj),
        key = null,
        temp = null;

    for (let i = 0; i < keys.length; i++) {
        key = keys[i];    
        temp = obj[key];
        // 如果字段的值也是一个对象则递归操作
        if (temp && typeof temp === 'object') {
            result[key] = deepCopy(temp);
        } else {
        // 否则直接赋值给新对象
            result[key] = temp;
        }
    }
    return result;
}

var obj1 = {
    x: {
        m: 1
    },
    y: undefined,
    z: function add(z1, z2) {
        return z1 + z2
    },
    a: Symbol("foo")
};

var obj2 = deepCopy(obj1);
obj2.x.m = 2;

console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}
console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}

可以看到,递归完美的解决了前面遗留的所有问题,我们也可以用第三方库: jquery的$.extend和lodash的_.cloneDeep来解决深拷贝。上面虽然是用Object验证 ,但对于Array也同样适用,因为Array也是特殊的Object。

到这里,深拷贝问题基本可以告一段落了。但是,还有一个非常特殊的场景:

循环引用拷贝

var obj1 = {
    x: 1, 
    y: 2
};
obj1.z = obj1;

var obj2 = deepCopy(obj1);

此时如果调用刚才的deepCopy函数的话,会陷入一个循环的递归过程,从而导致爆栈。jquery的$.extend也没有解决。解决这个问题也非常简单,只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可,修改一下代码

function deepCopy(obj, parent = null) {
        // 创建一个新对象
        let result = {};
        let keys = Object.keys(obj),
            key = null,
            temp= null,
            _parent = parent;
        // 该字段有父级则需要追溯该字段的父级
        while (_parent) {
            // 如果该字段引用了它的父级则为循环引用
            if (_parent.originalParent === obj) {
                // 循环引用直接返回同级的新对象
                return _parent.currentParent;
            }
            _parent = _parent.parent;
        }
        for (let i = 0; i < keys.length; i++) {
            key = keys[i];
            temp= obj[key];
            // 如果字段的值也是一个对象
            if (temp && typeof temp=== 'object') {
                // 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用
                result[key] = DeepCopy(temp, {
                    originalParent: obj,
                    currentParent: result,
                    parent: parent
                });
    
            } else {
                result[key] = temp;
            }
        }
        return result;
    }
    
    var obj1 = {
        x: 1, 
        y: 2
    };
    obj1.z = obj1;
    
    var obj2 = deepCopy(obj1);
    console.log(obj1); //太长了去浏览器试一下吧~ 
    console.log(obj2); //太长了去浏览器试一下吧~