关于JavaScript中的浅拷贝和深拷贝

时间:2023-01-02 19:48:10

前言

在JavaScript中,目前的基本类型是:Boolean、Null、Undefined、Number、String、Symbol(ES6),引用类型有Object、Array、Function。对于基本类型的值,我们来看下面这个例子:

//例子1
var a = 10,b = a;
a = 20 ;
console.log(a);  //20
console.log(b); //10

可以看到b并没有随a的值改变而改变。而对于引用类型来说,我们看下面这个例子:

//例子2
var obj1 = { a:10 , b: 20};
var obj2 = obj1;
console.log(obj1);  //{a: 10, b: 20}
console.log(obj2);  //{a: 10, b: 20}

obj2.b = 50;
console.log(obj1);  //{a: 10, b: 50}
console.log(obj2);  //{a: 10, b: 50}

是不是有点出乎意料呢,obj1竟然随着obj2的改变而改变了。我们需要理解到两种类型的区别:基本类型是按值传递的,其保存在栈内存中,而引用类型是按引用传递的,其引用存放在栈区,实际对象保存在堆区。
借用别人的比方来更好的理解:

基本类型和引用类型在赋值上的区别可以按“连锁店”和“单店”来理解:基本类型赋值等于在一个新的地方安装连锁店的规范标准新开一个分店,新开的店与其他旧店互不相关,各自运营;而引用类型赋值相当于一个店有两把钥匙,交给两个老板同时管理,两个老板的行为都有可能对一间店的运营造成影响。

所以,浅拷贝和深拷贝的概念只在引用类型中出现,在基本类型中是没有的。


浅拷贝和深拷贝

浅拷贝只会复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
关于JavaScript中的浅拷贝和深拷贝
现在我们知道了浅拷贝和深拷贝的由来,其实上面例子2就是一个典型的浅拷贝,那么,我们要如何实现深拷贝呢?我们来看看Array和Object的自带方法是否支持。

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

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

//Object
var obj1 = { a:10 , b: 20};
var obj2 = Object.assign({},obj1);
console.log(obj1);  //{a: 10, b: 20}
console.log(obj2);  //{a: 10, b: 20}

obj2.a = 30 ;
console.log(obj1);  //{a: 10, b: 20}
console.log(obj2);  //{a: 30, b: 20}

通过上面的两个例子,我们好像已经成功的实现了深拷贝,但是如果改成二维数组和对象时:

//Array
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] [0]= 5;
console.log(arr1);  //[1, 2, [5, 4]]
console.log(arr2);  //[1, 2, [5, 4]]

//Object
var obj1 = { 
    a:10 , 
    b: {
        c : 20
    }};
var obj2 = Object.assign({},obj1);
console.log(obj1);  //{a: 10, b: {c: 20}}
console.log(obj2);  //{a: 10, b: {c: 20}}

obj2.b.c = 30 ;
console.log(obj1);  //{a: 10, b: {c: 30}}
console.log(obj2);  //{a: 10, b: {c: 30}}

可以发现,上面都变成浅拷贝了。经过上面的测试,slice()只能实现一维数组的深拷贝,具备同等特性的还有concat、Array.from() ,同样的,Object.assign()也只能实现一维对象的深拷贝
JavaScript本身的方法很难彻底解决Array和Object的深拷贝问题,所以我们要用递归的方式来解决

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

    for (var 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
    }
};

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

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

递归方案解决了所有的问题。因为Array也是特殊的Object,所以对于上例仍然适用。
另外,jQuery库提供了$.extend用来实现深拷贝的功能:

var obj1 = {  //记得先引入jQuery库
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f);  // false

另外一个函数库lodash,同样提供了_.cloneDeep用来做 深拷贝。

var obj1 = {  //先引入lodash库
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false

以上就是全部内容,欢迎讨论。