天天看點

JS中深拷貝數組、對象、對象數組方法

數組及對象 深拷貝和淺拷貝

我們在JS程式中需要進行頻繁的變量指派運算,對于字元串、布爾值等可直接使用指派運算符 “=” 即可,但是對于數組、對象、對象數組的拷貝,我們需要了解更多的内容。

首先,我們需要了解JS的淺拷貝與深拷貝的差別。

我們先給出一個數組:

var arr = ["a","b"];      

現在怎麼建立一份arr數組的拷貝呢?直接執行指派運算嗎?我們來看看輸出結果

var arrCopy = arr;
arrCopy[1] = "c";
arr   // => ["a","c"]      

可以發現對拷貝數組 arrCopy 進行操作時原數組也相應地被改變了,這就是JS的淺拷貝模式。是以我們可以指出對數組、對象、對象數組進行簡單指派運算隻是建立了一份原内容的引用,指向的仍然是同一塊記憶體區域,修改時會對應修改原内容,而有時候我們并不需要這種模式,這就需要對内容進行深拷貝。

1 數組的深拷貝

 對于數組的深拷貝正常的有幾種方法:

方法一:周遊複制

1 var arr = ["a", "b"], arrCopy = [];
2 for (var item in arr) arrCopy[item] = arr[item];
3 arrCopy[1] = "c";
4 arr   // => ["a", "b"]
5 arrCopy   // => ["a", "c"]      

考慮僞多元數組可以寫成函數形式:

function arrDeepCopy(source){
    var sourceCopy = [];
    for (var item in source) sourceCopy[item] = typeof source[item] === 'object' ? arrDeepCopy(source[item]) : source[item];
    return sourceCopy;
}      

這種方法簡單粗暴,但是利用JS本身的函數我們可以更加便捷地實作這個操作。

方法二:slice()

可以參考 W3School 對 slice() 方法的描述:slice() 方法可從已有的數組中傳回標明的元素。

調用格式為:

arrayObject.slice(start,end)      

方法傳回一個新的數組,包含從 start 到 end (不包括該元素)的 arrayObject 中的元素。該方法并不會修改數組,而是傳回一個子數組。

在這裡我們的思路是直接從數組開頭截到尾:

arrCopy = arr.slice(0);
arrCopy[1] = "c";
arr   // => ["a", "b"] 
arrCopy   // => ["a", "c"]      

可以看出成功建立了一份原數組的拷貝。

方法三:concat()

可以參考 W3School 對 concat() 方法的描述:concat() 方法用于連接配接兩個或多個數組。

調用格式為:

arrayObject.concat(arrayX,arrayX,......,arrayX)      

該方法不會改變現有的數組,而僅僅會傳回被連接配接數組的一個副本。

使用這種方法的思路是我們用原數組去拼接一個空内容,放回的便是這個數組的拷貝:

arrCopy = arr.concat();
arrCopy[1] = "c";
arr   // => ["a", "b"] 
arrCopy   // => ["a", "c"]      

方法四:擴充運算符(...)

es6新增了擴充運算符,把數組拆開再組合成新的數組:

arrCopy = [...arr];
arrCopy[1] = "c";
arr   // => ["a", "b"] 
arrCopy   // => ["a", "c"]      

2、對象的深拷貝

 對于數組的深拷貝我們有了概念,那麼一般對象呢?

我們給出一個對象:

var obj = { "a": 1, "b": 2 };      

同樣做測試:

var objCopy = obj;
objCopy.b = 3;
obj   // => { "a": 1, "b": 3 }
objCopy   // => { "a": 1, "b": 3 }      

同樣,簡單的指派運算隻是建立了一份淺拷貝。

而對于對象的深拷貝,沒有内置方法可以使用,我們可以自己命名一個函數進行這一操作:

var objDeepCopy = function(source){
    var sourceCopy = {};
    for (var item in source) sourceCopy[item] = source[item];
    return sourceCopy;
}      

但是對于複雜結構的對象我們發現這個函數并不适用,例如:

var obj = { "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 };      

是以需要進行一點修改:

var objDeepCopy = function(source){
    var sourceCopy = {};
    for (var item in source) sourceCopy[item] = typeof source[item] === 'object' ? objDeepCopy(source[item]) : source[item];
    return sourceCopy;
}
var objCopy = objDeepCopy(obj);
objCopy.a.a1[1] = "a13";
obj   // => { "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 }
objCopy   // => { "a": { "a1": ["a11", "a13"], "a2": 1 }, "b": 2 }      

3、對象數組的深拷貝

 如果再考慮更奇葩更複雜的情況,例如我們定義:

var obj = [{ "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 }, ["c", { "d": 4, "e": 5 }]];      

這是一個由對象、數組雜合成的奇葩數組,雖然我們平時寫程式基本不可能這麼折騰自己,但是可以作為一種特殊情況來考慮,這樣我們就可以結合之前說的方法去拓展拷貝函數:

var objDeepCopy = function (source) {
    var sourceCopy = source instanceof Array ? [] : {};
    for (var item in source) {
        sourceCopy[item] = typeof source[item] === 'object' ? objDeepCopy(source[item]) : source[item];
    }
    return sourceCopy;
}
var objCopy = objDeepCopy(obj);
objCopy[0].a.a1[1] = "a13";
objCopy[1][1].e = "6";
obj   // => [{ "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 }, ["c", { "d": 4, "e": 5 }]]
objCopy   // => [{ "a": { "a1": ["a11", "a13"], "a2": 1 }, "b": 2 }, ["c", { "d": 4, "e": 6 }]]      

參考文檔 https://www.cnblogs.com/jiangzilong/p/6513552.html

首先,我們需要了解JS的淺拷貝與深拷貝的差別。

我們先給出一個數組:

var arr = ["a","b"];      

現在怎麼建立一份arr數組的拷貝呢?直接執行指派運算嗎?我們來看看輸出結果

var arrCopy = arr;
arrCopy[1] = "c";
arr   // => ["a","c"]      

可以發現對拷貝數組 arrCopy 進行操作時原數組也相應地被改變了,這就是JS的淺拷貝模式。是以我們可以指出對數組、對象、對象數組進行簡單指派運算隻是建立了一份原内容的引用,指向的仍然是同一塊記憶體區域,修改時會對應修改原内容,而有時候我們并不需要這種模式,這就需要對内容進行深拷貝。

1 數組的深拷貝

 對于數組的深拷貝正常的有幾種方法:

方法一:周遊複制

1 var arr = ["a", "b"], arrCopy = [];
2 for (var item in arr) arrCopy[item] = arr[item];
3 arrCopy[1] = "c";
4 arr   // => ["a", "b"]
5 arrCopy   // => ["a", "c"]      

考慮僞多元數組可以寫成函數形式:

function arrDeepCopy(source){
    var sourceCopy = [];
    for (var item in source) sourceCopy[item] = typeof source[item] === 'object' ? arrDeepCopy(source[item]) : source[item];
    return sourceCopy;
}      

這種方法簡單粗暴,但是利用JS本身的函數我們可以更加便捷地實作這個操作。

方法二:slice()

可以參考 W3School 對 slice() 方法的描述:slice() 方法可從已有的數組中傳回標明的元素。

調用格式為:

arrayObject.slice(start,end)      

方法傳回一個新的數組,包含從 start 到 end (不包括該元素)的 arrayObject 中的元素。該方法并不會修改數組,而是傳回一個子數組。

在這裡我們的思路是直接從數組開頭截到尾:

arrCopy = arr.slice(0);
arrCopy[1] = "c";
arr   // => ["a", "b"] 
arrCopy   // => ["a", "c"]      

可以看出成功建立了一份原數組的拷貝。

方法三:concat()

可以參考 W3School 對 concat() 方法的描述:concat() 方法用于連接配接兩個或多個數組。

調用格式為:

arrayObject.concat(arrayX,arrayX,......,arrayX)      

該方法不會改變現有的數組,而僅僅會傳回被連接配接數組的一個副本。

使用這種方法的思路是我們用原數組去拼接一個空内容,放回的便是這個數組的拷貝:

arrCopy = arr.concat();
arrCopy[1] = "c";
arr   // => ["a", "b"] 
arrCopy   // => ["a", "c"]      

方法四:擴充運算符(...)

es6新增了擴充運算符,把數組拆開再組合成新的數組:

arrCopy = [...arr];
arrCopy[1] = "c";
arr   // => ["a", "b"] 
arrCopy   // => ["a", "c"]      

2、對象的深拷貝

 對于數組的深拷貝我們有了概念,那麼一般對象呢?

我們給出一個對象:

var obj = { "a": 1, "b": 2 };      

同樣做測試:

var objCopy = obj;
objCopy.b = 3;
obj   // => { "a": 1, "b": 3 }
objCopy   // => { "a": 1, "b": 3 }      

同樣,簡單的指派運算隻是建立了一份淺拷貝。

而對于對象的深拷貝,沒有内置方法可以使用,我們可以自己命名一個函數進行這一操作:

var objDeepCopy = function(source){
    var sourceCopy = {};
    for (var item in source) sourceCopy[item] = source[item];
    return sourceCopy;
}      

但是對于複雜結構的對象我們發現這個函數并不适用,例如:

var obj = { "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 };      

是以需要進行一點修改:

var objDeepCopy = function(source){
    var sourceCopy = {};
    for (var item in source) sourceCopy[item] = typeof source[item] === 'object' ? objDeepCopy(source[item]) : source[item];
    return sourceCopy;
}
var objCopy = objDeepCopy(obj);
objCopy.a.a1[1] = "a13";
obj   // => { "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 }
objCopy   // => { "a": { "a1": ["a11", "a13"], "a2": 1 }, "b": 2 }      

3、對象數組的深拷貝

 如果再考慮更奇葩更複雜的情況,例如我們定義:

var obj = [{ "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 }, ["c", { "d": 4, "e": 5 }]];      

這是一個由對象、數組雜合成的奇葩數組,雖然我們平時寫程式基本不可能這麼折騰自己,但是可以作為一種特殊情況來考慮,這樣我們就可以結合之前說的方法去拓展拷貝函數:

var objDeepCopy = function (source) {
    var sourceCopy = source instanceof Array ? [] : {};
    for (var item in source) {
        sourceCopy[item] = typeof source[item] === 'object' ? objDeepCopy(source[item]) : source[item];
    }
    return sourceCopy;
}
var objCopy = objDeepCopy(obj);
objCopy[0].a.a1[1] = "a13";
objCopy[1][1].e = "6";
obj   // => [{ "a": { "a1": ["a11", "a12"], "a2": 1 }, "b": 2 }, ["c", { "d": 4, "e": 5 }]]
objCopy   // => [{ "a": { "a1": ["a11", "a13"], "a2": 1 }, "b": 2 }, ["c", { "d": 4, "e": 6 }]]      

參考文檔 https://www.cnblogs.com/jiangzilong/p/6513552.html