JS深拷貝總結
JS的原生方法不支持深拷貝,
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);
結果:

over
原文:https://juejin.im/post/5b20c9f65188257d7d719c1c
