深入淺出的“深拷貝與淺拷貝”

不願意透露姓名的聶先生發表於2018-07-24

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裡的值是不會跟著發生改變的。這裡涉及到了遞迴呼叫,有不明白的童鞋可以看下遞迴相關的資料。這就完成了對一個物件的深拷貝。

淺拷貝比較容易理解,當然深拷貝也是容易理解的,只是得注意拷貝上面說的那種情況。就沒問題了。有任何問題,歡迎提問

相關文章