本文分三部分
1.先說什么是深復(fù)制淺復(fù)制 在定義上進(jìn)行區(qū)分
2.深復(fù)制淺復(fù)制的各個(gè)應(yīng)用場景
3.總結(jié)淺復(fù)制深復(fù)制優(yōu)缺點(diǎn)
4.如何實(shí)現(xiàn)深復(fù)制
5.json實(shí)現(xiàn)深復(fù)制的注意事項(xiàng)
一.深復(fù)制淺復(fù)制的定義概念區(qū)分
什么是淺復(fù)制?
文字定義:就是原對象里有屬性也是一個(gè)對象,當(dāng)在復(fù)制時(shí),這個(gè)對象屬性引用值沒有發(fā)生變化,還是指向原對象的對象屬性。
代碼表示:
const originObj={a:1,objAttr:{b:2}};
const newObj={...originObj};
newObj.objAttr===originObj.objAttr///比較結(jié)果是true, newObj.objAttr對象引用值沒有變化,還是指向原對象屬性
什么是深復(fù)制
文字定義:就是原對象里有屬性也是一個(gè)對象,當(dāng)在復(fù)制時(shí),這個(gè)對象屬性引用值發(fā)生變化被賦予新值,不指向原對象的對象屬性。
代碼表示:
const originObj={a:1,objAttr:{b:2}};
const newObj=JSON.parse(JSON.stringify(orginObj));
newObj.objAttr===originObj.objAttr///比較結(jié)果是false, newObj.objAttr對象引用值變化,不指向原對象屬性
二.深復(fù)制淺復(fù)制的應(yīng)用場景
深度復(fù)制應(yīng)用場景
深復(fù)制 主要作用是隔離兩個(gè)相關(guān)變量的相互影響,有哪些場景要注意隔離影響呢
1.常規(guī)就是函數(shù)傳遞參數(shù)時(shí),隔離相互影響;函數(shù)調(diào)用要傳遞一個(gè)對象數(shù)據(jù)時(shí),養(yǎng)成深克隆后傳遞的習(xí)慣;不要希望別人沒有修改你的原對象;另外代碼會更加穩(wěn)定。多人協(xié)作寫代碼,可能此刻是好的,下一個(gè)月就有人修改了你調(diào)用的函數(shù),修改了你傳遞進(jìn)來的對象。導(dǎo)致莫名錯(cuò)誤。
如下面代碼
function mdy(obj){
obj.subObj.xx=123;//函數(shù)內(nèi)部修改了傳遞進(jìn)來的對象 如果是你是深克隆后傳遞進(jìn)來 就不用超心。
}
2.在react的hook里 如useEffect 依賴一個(gè)狀態(tài)是對象時(shí),只有對象引用發(fā)生變化,這個(gè)useEffect才會執(zhí)行
const [stateData,setStateData]= useState({obj:{},xx:3})
useEffect(()=>{
//do something
},[ stateData.obj ] );//這個(gè)時(shí)候 stateData每次設(shè)置就必須是深克隆
在react里應(yīng)用還很多的,例如是否應(yīng)該更新渲染 的判斷 比較的也是引用是否發(fā)生了變化。另外全局狀態(tài)的追蹤記錄 也是引用比較。
在對象要進(jìn)行引用比較時(shí) 深克隆比較安全
淺復(fù)制應(yīng)用場景
1.兩種復(fù)制方式都能解決問題時(shí),就采用淺復(fù)制,因?yàn)?strong>淺復(fù)制性能更好。
什么情況下會出現(xiàn)淺復(fù)制解決問題就好
比如下面代碼
const obj={a:1,b:{c:3}}
//你關(guān)注的值只有a時(shí),不關(guān)注b時(shí),就采用淺復(fù)制。即只關(guān)心基本類型的數(shù)據(jù),不關(guān)心引用類型的數(shù)據(jù)時(shí)。采用淺復(fù)制
2.必須采用淺復(fù)制的場景
就是對象之間可以共享一個(gè)公共對象,通過共享公共對象可以節(jié)省內(nèi)存,也更好的實(shí)現(xiàn)協(xié)同。
例如一般每個(gè)學(xué)生都有一個(gè)相同的班主任。這個(gè)時(shí)候通過淺復(fù)制學(xué)生對象再生產(chǎn)出一個(gè)學(xué)生對象,兩個(gè)學(xué)生對象之間就能夠共享班主任對象了。 代碼表示如下
const stdA={age:10,name:'a' sex:boy,headTeacher:{name:"liwei",sex:'gril' }};
const stdB={...stdA};//通過淺復(fù)制出來學(xué)生b就和學(xué)生a有了相同的班主任。
stdB.age=12;
stdB.name='b'
如果有成千上萬個(gè)學(xué)生共享一個(gè)校長對象,那淺復(fù)制節(jié)約內(nèi)存的效果就很突出了。
三.總結(jié)淺復(fù)制深復(fù)制優(yōu)缺點(diǎn)比較
上面已經(jīng)對各個(gè)場景進(jìn)行了細(xì)說。
總結(jié)記憶可以是:淺復(fù)制執(zhí)行效率高,共用內(nèi)存省內(nèi)存;深復(fù)制隔離影響強(qiáng)。
所以寫代碼時(shí)你是想共用還是想隔離就要思考清楚。
四.深復(fù)制的實(shí)現(xiàn)方法
實(shí)現(xiàn)時(shí)要注意3個(gè)問題
1.對 基本類型 函數(shù)類型 引用類型的判斷
2.要寫一個(gè)深度遞歸,以及注意采用尾遞歸來優(yōu)化遞歸性能
3.要解決循環(huán)引用問題
下面是一個(gè)具體實(shí)現(xiàn)
//整體想法 對基礎(chǔ)類型 函數(shù)類型 引用類型分情況處理,用weakMap處理循環(huán)引用的情況
const deepClone = (obj, distObj = {}, weakMap = new WeakMap()) => {
if (!obj || typeof obj !== "object") {
return distObj;
}
if (!weakMap.has(obj)) {
weakMap.set(obj, distObj);
}
Object.keys(obj).forEach((key) => {
const keyValue = obj[key];
//函數(shù)的情況
if (typeof keyValue === "function") {
distObj[key] = keyValue;
//對象情況
} else if (keyValue && typeof keyValue === "object") {
//說明還沒有發(fā)生循環(huán)引用
if (!weakMap.has(keyValue)) {
//復(fù)制引用對象
const dt = { ...keyValue };
distObj[key] = dt;
//把值存入weakMap
weakMap.set(keyValue, dt);
deepClone(dt, dt, weakMap);
} else {
//說明發(fā)生了循環(huán)引用
distObj[key] = weakMap.get(keyValue);
}
} else {
//基礎(chǔ)類型情況
distObj[key] = keyValue;
}
});
return distObj;
};
//測試用例 構(gòu)造循環(huán)引用
const ddd = { aa: 44, c: 3, fun: () => 0 };
const ttt = { d: 4, g: { d: ddd } };
ddd.t = ttt;
console.log("克隆結(jié)果", deepClone(ttt));
淺復(fù)制實(shí)現(xiàn)的兩個(gè)方法(上面的代碼能夠?qū)崿F(xiàn)淺復(fù)制)。
const obj={a:1,c:{d:2}};
const obj2={...obj};/// es6 對象擴(kuò)展... 簡單實(shí)現(xiàn)淺復(fù)制
const obj3=Object.assign({},obj);//Object.assign 這個(gè)api實(shí)現(xiàn)淺復(fù)制
obj.c===obj2.c===obj3.c;//三個(gè)都相對
五.常用的json實(shí)現(xiàn)深復(fù)制注意事項(xiàng)
1.underfine值不能復(fù)制
2.函數(shù)不能復(fù)制
3.循環(huán)引用問題無法解決,會報(bào)異常,需要做錯(cuò)誤處理
具體可以看看別人的 http://www.lxweimin.com/p/52db1d0c1780