天天看點

JavaScript 的深拷貝和淺拷貝JavaScript 的深拷貝和淺拷貝

JavaScript 的深拷貝和淺拷貝

在 JavaScript 的變量指派操作中,如果一個變量值是簡單類型,直接複制沒有問題。但如果是對象或者數組,直接複制後的新對象或者數組隻要一修改,原對象或者數組就會同樣跟着被修改。如果你不了解深拷貝和淺拷貝,你可能就會覺得這是 bug,不可了解。但是看完本文你就能了解了。

什麼是深拷貝和淺拷貝?

淺拷貝比較容易了解,是以先從淺拷貝開始說起吧!

淺拷貝

就是将一個對象(或數組)的記憶體位址『編号』複制給另一個對象(或數組)

深拷貝

增加一個指針,并且申請一個新的記憶體位址,使這個增加的指針指向這個新的記憶體,然後将原變量對應記憶體位址裡的值逐個複制過去

深拷貝和淺拷貝怎麼實作?

數組

eg1. 淺拷貝

var arr = ["One", "Two", "Three"];
console.log("原數組的值:" + arr);
var newArr = arr;
newArr[] = "newTwo";
console.log("新數組的值:" + newArr);
console.log("淺拷貝後,原數組的值:" + arr);
           

運作結果:

JavaScript 的深拷貝和淺拷貝JavaScript 的深拷貝和淺拷貝

因為 arr 淺拷貝指派給 newArr 的是一個指向 arr 記憶體位址的引用,是以修改 newArr 的第2個元素的值,會将原數組 arr 的第二個元素值也修改掉了(其實在記憶體裡,兩個數組共用一個記憶體空間)。

eg2. 深拷貝

var arr = ["One", "Two", "Three"];
console.log("原數組的值:" + arr);
var newArr = arr.slice();
newArr[] = "newTwo";
console.log("新數組的值:" + newArr);
console.log("深拷貝後,原數組的值:" + arr);
           

運作結果:

JavaScript 的深拷貝和淺拷貝JavaScript 的深拷貝和淺拷貝

因為深拷貝是給 newArr 申請一個新的記憶體位址,并使 newArr 的指針指向這個新的記憶體。然後再把 arr 對應記憶體的值逐個複制到這個新記憶體位址。是以這時再修改 newArr 的第2個元素的值,就不會将原數組 arr 的第二個元素值也修改掉了(在記憶體裡,兩個數組不再共用一個記憶體空間了)。

對象

eg1. 淺拷貝

var obj = {
    name: 'xu',
    age: ,
    sex: 'men',
}
console.log("原對象的值:");
console.log(obj);
var newObj = obj;
newObj.age = ;
console.log("新對象的值:");
console.log(newObj);
console.log("淺拷貝後,原對象的值:");
console.log(obj);
           

運作結果:

JavaScript 的深拷貝和淺拷貝JavaScript 的深拷貝和淺拷貝

對象的淺拷貝跟數組一樣,因為指派過去的是原對象的引用,是以修改了新對象 newObj 的屬性 age 後,原對象屬性 age 也被修改了。

eg2. 深拷貝

var obj = {
    name: 'xu',
    age: ,
    sex: 'men',
}
console.log("原對象的值:");
console.log(obj);
var newObj = {};
for (var i in obj) {
    newObj[i] = obj[i];
}
newObj.age = ;
console.log("新對象的值:");
console.log(newObj);
console.log("深拷貝後,原對象的值:");
console.log(obj);
           

運作結果:

JavaScript 的深拷貝和淺拷貝JavaScript 的深拷貝和淺拷貝

這是針對一個對象中的所有屬性值都是基本資料類型的情況。但是如果一個對象的屬性值還是對象這種情況,就需要遞歸調用淺拷貝。可以如下處理:

eg. 對象深拷貝拓展

function deepCopy(obj, copyObj) {
    var newObj = copyObj || {};
    for (var i in obj) {
        if (typeof obj[i] === 'object') {
            newObj[i] = (obj[i].constructor === Array) ? [] : {};
            deepCopy(obj[i], newObj[i]);
        } else {
            newObj[i] = obj[i];
        }
    }
    return newObj;
}

var obj = {
    name: {
        oldName: 'pan',
        newName: 'zhengmao'
    },
    age: ,
    sex: 'men',
}
console.log("原對象的 name 屬性值:");
console.log(obj.name);
var secObj = {};
secObj = deepCopy(obj, secObj);             // 将 obj 的所有屬性都複制到 secObj 中
secObj.name = {
    oldName: 'pan2',
    newName: 'zhengmao2'
}
console.log("新對象的的 name 屬性值:");
console.log(secObj.name);
console.log("深拷貝後,原對象的 name 屬性值:");
console.log(obj.name);
           

運作結果:

JavaScript 的深拷貝和淺拷貝JavaScript 的深拷貝和淺拷貝

實作原理,其實就是先建立一個空對象,在記憶體中新開辟一塊位址,把被複制對象的所有可枚舉的(注意可枚舉的對象)屬性方法一一複制過來,注意要用遞歸來複制子對象裡面的所有屬性和方法,直到所有子代屬性都為基本資料類型。

對象深拷貝的方法還有不少:

  1. 對象拷貝方法:

    b = Object.assign({}, a)

  2. 字元串對象互轉方法結合:

    b = JSON.parse( JSON.stringify(a) )

結論

對于簡單的資料類型(如 Number、String、Boolean 等),JavaScript 變量指派即是值複制(深拷貝)。但是對象或者數組,直接指派其實複制的隻是記憶體位址(淺拷貝)。這樣錯誤的操作會讓父對象或父數組存随時在被篡改的可能。這時,如需實作深拷貝則需要跟文中示例一樣,自己另行處理。

繼續閱讀