Object.assign
和{...obj}
都屬於淺拷貝,下面我們講解如何使用JS實現深拷貝。JSON.sringify 和 JSON.parse
這是JS實現深拷貝最簡單的方法了,原理就是先將物件轉換為字串,再通過JSON.parse重新建立一個物件。 但是這種方法的侷限也很多:
- 不能複製function、正則、Symbol
- 迴圈引用報錯
- 相同的引用會被重複複製
我們依次看看這三點,我們測試一下這段程式碼:
let obj = {
reg : /^asd$/,
fun: function(){},
syb:Symbol('foo'),
asd:'asd'
};
let cp = JSON.parse(JSON.stringify(obj));
console.log(cp);複製程式碼
結果:
可以看到,函式、正則、Symbol都沒有被正確的複製。
如果在JSON.stringify
中傳入一個迴圈引用的物件,那麼會直接報錯:
在說第三點之前,我們看看這段程式碼:
let obj = { asd:'asd' };
let obj2 = {name:'aaaaa'};
obj.ttt1 = obj2;
obj.ttt2 = obj2;
let cp = JSON.parse(JSON.stringify(obj));
obj.ttt1.name = 'change';
cp.ttt1.name = 'change';
console.log(obj,cp);複製程式碼
在原物件 obj 中的 ttt1 和 ttt2 指向了同一個物件 obj2,那麼我在深拷貝的時候,就應該只拷貝一次 obj2 ,下面我們看看執行結果:
我們可以看到(上面的為原物件,下面的為複製物件),原物件改變 ttt1.name 也會改變 ttt2.name ,因為他們指向相同的物件。
但是,複製的物件中,ttt1 和 ttt2 分別指向了兩個物件。複製物件沒有保持和原物件一樣的結構。因此,JSON實現深複製不能處理指向相同引用的情況,相同的引用會被重複複製。
遞迴實現
JS原生的方法不能很好的實現深複製,那麼我們就動手實現一個。
思想非常簡單:對於簡單型別,直接複製。對於引用型別,遞迴複製它的每一個屬性。
我們需要解決的問題:
- 迴圈引用
- 相同引用
- 不同的型別(筆者僅實現了陣列和物件的區分)
實現程式碼:
function deepCopy(target){
let copyed_objs = [];//此陣列解決了迴圈引用和相同引用的問題,它存放已經遞迴到的目標物件
function _deepCopy(target){
if((typeof target !== 'object')||!target){return target;}
for(let i= 0 ;i<copyed_objs.length;i++){
if(copyed_objs[i].target === target){
return copyed_objs[i].copyTarget;
}
}
let obj = {};
if(Array.isArray(target)){
obj = [];//處理target是陣列的情況
}
copyed_objs.push({target:target,copyTarget:obj})
Object.keys(target).forEach(key=>{
if(obj[key]){ return;}
obj[key] = _deepCopy(target[key]);
});
return obj;
}
return _deepCopy(target);
}
複製程式碼
copyed_objs 這個陣列存放的是已經遞迴過的目標物件。在遞迴一個目標物件之前,我們應該檢查這個陣列,如果當前目標物件和 copyed_objs 中的某個物件相等,那麼不對其遞迴。
這樣就解決了迴圈引用和相同引用的問題。
測試一下程式碼:
var a = {
arr:[1,2,3,{key:'123'}],//陣列測試
};
a.self = a;//迴圈引用測試
a.common1 = {name:'ccc'};
a.common2 = a.common1;//相同引用測試
var c = deepCopy(a);
c.common1.name = 'changed';
console.log(c);複製程式碼
結果:
可以看到,前文提到的問題都已經解決。
最後補充:
本文實現的深拷貝僅僅是解決了深複製的關鍵問題,還需要針對不同的資料型別進行完善。