深拷貝與淺拷貝

  淺拷貝java

  複製一層對象的屬性,並不包括對象裏面的爲引用類型的數據,當改變拷貝的對象裏面的引用類型時,源對象也會改變。數組

  深拷貝瀏覽器

  從新開闢一個內存空間,須要遞歸拷貝對象裏的引用,直到子屬性都爲基本類型。兩個對象對應兩個不一樣的地址,修改一個對象的屬性,不會改變另外一個對象的屬性。spa

  javaScript的變量類型指針

  (1)基本類型:
    5種基本數據類型Undefined、Null、Boolean、Number 和 String,變量是直接按值存放的,存放在棧內存中的簡單數據段,能夠直接訪問。code

 


  (2)引用類型:
    存放在堆內存中的對象,變量保存的是一個指針,這個指針指向另外一個位置。當須要訪問引用類型(如對象,數組等)的值時,首先從棧中得到該對象的地址指針,而後再從堆內存中取得所需的數據。對象

  簡述兩種類型的存儲方式:blog

   a基本類型(深拷貝)--名值存儲在棧內存中,例如let a=1;遞歸

棧內存
a 1

       當b=a複製時,棧內存會開闢一個內存ip

棧內存
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}}
相關文章
相關標籤/搜索