深入浅出的“深拷贝与浅拷贝”

1,171 阅读4分钟

js中的浅拷贝与深拷贝,只是针对复杂数据类型(object, Array)的复制问题。浅拷贝和深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储在堆内存中然后通过一个引用值只操作对象,由此拷贝的时候存在两种情况:拷贝引用和拷贝实例,也就是咱们今天要讨论的浅拷贝和深拷贝。


  • 浅拷贝 : 浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象实例,彼此之间操作都会有影响
  • 深拷贝 :在堆中重新分配内存,并且把源对象所有属性都进行进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原对象上的对象引用图上的任何对象,拷贝后的对象原对象完全隔离互不影响。

浅拷贝

浅拷贝分两种情况,拷贝直接拷贝源对象的引用和源对象拷贝实例,但其属性拷贝引用

拷贝原的引用

这是最简单的浅拷贝。例:

let a = {c:1};
let b = a;
console.log(a === b) //true
a.c = 2
console.log(b.c) // 2

源对象拷贝实例,其属性对象拷贝引用

这种情况,外层源对象是拷贝实例,如果其属性元素为复杂数据类型时,内层元素拷贝引用。 对源对象直接操作,不影响两外一个对象,但是对其属性操作时,会改变两外一个对象的属性的只。 常用方法为:Array.prototype.slice(),Array.prototype.concat(), jQury$.extend({},obj),例:

let a = [{c: 1},{d: 2}];
let b = a.slice();
console.log(a === b) // false 说明外层数组拷贝的是实例
a[0].c = 3
console.log(b[0].c) // 3 说明其元素拷贝是引用

深拷贝

深拷贝后,两个对象,包括其内部的元素互不干扰。常见方法有JSON.parse(),JSON.stringify(),jquery$.extend(true, {}, obj),lodash_.cloneDeep和_.clone(value, true)。例:

var a = {c: {d: 1}};
var b = $.extend(true, {}, a);
console.log(a === b); // 输出false
a.c.d = 3;
console.log(b.c.d); // 输出 1,没有改变。

深拷贝就是增加一个“指针”,并申请一个新的内存,并且让这个新增加的“指针”指向这个新的内存地址,使用深拷贝,在释放内存的时候就不会像浅拷贝一样出现重复释放同一段内存的错误,当我们需要复制原对象而又不能修改元对象的时候,深拷贝就是一个,也是唯一的选择。我们来看一下例子

var arrayA = [ 1,2,3,4,5 ];
var arrayB = [] ;
arrayA.forEach ( function (element){
    arrayB.push(elemnt);
})
var str = 'hello'
arrayA.push(str) ;
console.log(arrayA);// [1, 2, 3, 4, 5, "abc"]
console.log(arrayB);// [1, 2, 3, 4, 5]

这里的arrayA和arrayB中的最后一个元素,这个元素是一个对象,指向的是同一段内存地址,所以当修改其中一个元素对象的值时,导致了另一个的值也跟着发生改变,但是如果新增加的元素不是一个对象,而是一个字符串,或者一个数字,这时候是没问题的。有问题我们是需要去解决的,对于第一种情况,我们使用同样的方式去解决它。既然新增加的元素是一个字符串或者一个数字的情况下,改变一个元素的值不会引发另一个元素的值的改变,所以我们就使用这种方式去解决,解决方案如下所示:

function copy( sourceObj , c) {
    var c = c || ( Array.isArray(sourceObj) ? [ ] : {} );
    for (var i in sourceObj) {
        if (typeof sourceObj[i] === 'object') {
            c[i] = Array.isArray(sourceObj[i])  ? [] : {};
            copy (sourceObj[i], c[i]);
        } else {
            c[i] = sourceObj[i];
        }
    }
    return c;
}
var arrayA = [1,2,3,4,5];
var  obj = {name:'Alex'};
arrayA.push(obj)
var arrayB = [];
copy(arrayA,arrayB);
arrayB[5].name = 'Tom'
console.log(arrayA);// [1, 2, 3, 4, 5, "Alex"]
console.log(arrayB);// [1, 2, 3, 4, 5, "Tom"]

我们先定义一个copy函数,传入两个参数,第一个参数是原对象,第二个参数是复制的对象,我们循环原对象,看原对象中的元素的类型是否是对象(Object),如果是的话我们再使用递归调用,copy这个对象,如果不是对象,直接赋值。最后返回copy后的对象,也就是这里的arrayB,当我们修改arrayB中的name的值时,arrayA里的值是不会跟着发生改变的。这里涉及到了递归调用,有不明白的童鞋可以看下递归相关的资料。这就完成了对一个对象的深拷贝。

浅拷贝比较容易理解,当然深拷贝也是容易理解的,只是得注意拷贝上面说的那种情况。就没问题了。有任何问题,欢迎提问