深拷贝与浅拷贝

  浅拷贝

  复制一层对象的属性,并不包括对象里面的为引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。

  深拷贝

  重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型。两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

  javaScript的变量类型

  (1)基本类型:
    5种基本数据类型Undefined、Null、Boolean、Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。


  (2)引用类型:
    存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

  简述两种类型的存储方式:

   a基本类型(深拷贝)--名值存储在栈内存中,例如let a=1;

栈内存
a 1

       当b=a复制时,栈内存会开辟一个内存

栈内存
a 1
b 1

  所以当你此时修改a=2,对b并不会造成影响。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

   b.引用数据类型(浅拷贝)--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值

栈内存 堆内存
name val val
a

 堆地址1

[0,1,2]

   当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。

    而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。

  浅拷贝的实现

  1)简单的引用复制

  

    function shallowClone(copyObj) {
        var obj = {};
        for (var i in copyObj) {
            obj[i] = copyObj[i];
        }
        return obj;
    }
    var x = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var y = shallowClone(x);
    x.b.f.g = 6
    console.log(y.b.f.g);  // 6

  2)Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象

    let obj1 = { a: 0, b: { c: 0 } };
    let obj2 = Object.assign({}, obj1);
    console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}

    obj2.b.c = 3;
    console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}}
    console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}}

  深拷贝的实现

  1)Array的slice和concat方法
     Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里,是因为它看起来像是深拷贝。而实际上它是浅拷贝。

    let a = [0, 1, [2, 3], 4],
        b = a.slice();
    a[0] = 1;
    a[2][0] = 1;
    console.log(a); // [1,1,[1,3],4]
    console.log(b); // [0,1,[1,3],4]

     从以上示例中可以看出拷贝的不彻底,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的深拷贝。

     第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。

     同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。

  2)利用递归复制所有层级属性,实现深拷贝

    function deepClone(obj) {
        let objClone = Array.isArray(obj) ? [] : {};
        if (obj && typeof obj === "object") {
            for (key in obj) {
                if (obj.hasOwnProperty(key)) {
                    //判断ojb子元素是否为对象,如果是,递归复制
                    if (obj[key] && typeof obj[key] === "object") {
                        objClone[key] = deepClone(obj[key]);
                    } else {
                        //如果不是,简单复制
                        objClone[key] = obj[key];
                    }
                }
            }
        }
        return objClone;
    }

    let a = [1, 2, 3, 4]
    b = deepClone(a);
    a[0] = 2;
    console.log(a);// [2,2,3,4]
    console.log(b);// [1,2,3,4]

  3)jQuery.extend()方法源码实现

     $.extend( [deep ], target, object1 [, objectN ] )

     deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝

     target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

     object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。

    let a = [0, 1, [2, 3], 4],
        b = $.extend(true, [], a);
    a[0] = 1;
    a[2][0] = 1;
    console.log(a)// [1,1,[1,3],4]
    console.log(b)// [0,1,[2,3],4]

  注意:jQuery的extend方法使用基本的递归思路实现了浅拷贝和深拷贝,但是这个方法也无法处理源对象内部循环引用,例如:

    var a = { "name": "aaa" };
    var b = { "name": "bbb" };
    a.child = b;
    b.parent = a;
    $.extend(true, {}, a);//直接报了栈溢出。Uncaught RangeError: Maximum call stack size exceeded

  4)JSON对象的parse和stringify
     JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深拷贝。

    obj1 = { a: 0, b: { c: 0 } };
    let obj3 = JSON.parse(JSON.stringify(obj1));
    obj1.a = 4;
    obj1.b.c = 4;
    console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}
    console.log(JSON.stringify(obj1)); // { a: 4, b: { c: 4}}

 

原文地址:https://www.cnblogs.com/Ann-web-1/p/11251599.html