淺拷貝只復(fù)制指向某個對象的引用嗜逻,而不復(fù)制對象本身,新舊對象還是共享同一塊內(nèi)存缭召;但深拷貝會另外創(chuàng)造一個一模一樣的對象栈顷,新對象跟原對象不共享內(nèi)存,它們是完全隔離嵌巷,互不影響的萄凤,對一個對象的修改并不會影響另一個對象。
區(qū)別:淺拷貝只復(fù)制對象的第一層屬性搪哪,深拷貝可以對對象的屬性進行遞歸復(fù)制靡努,直至屬性值為基本類型;
那么如何實現(xiàn)一個深拷貝呢晓折?
下面這段代碼通過遞歸復(fù)制惑朦,實現(xiàn)了一個初步的深拷貝函數(shù),但是并不完善漓概,具體見代碼行嗤。
// 1-簡單版
// 不足:(1)拷貝后的復(fù)雜數(shù)據(jù)類型只有對象和數(shù)組兩種
// (2)對于引用自身的屬性直接跳過
// (3)無法拷貝特殊類型 Date 、RegExp
function deepCopy(obj1) {
const obj2 = Array.isArray(obj1) ? [] : {};
if (obj1 && typeof obj1 === "object") {
for (let i in obj1) {
let prop = obj1[i]; // 避免相互引用造成死循環(huán)垛耳,如obj1.a=obj1
if (prop === obj1) {
continue;
}
// 僅拷貝對象自身屬性
if (obj1.hasOwnProperty(i)) {
// 如果子屬性為引用數(shù)據(jù)類型,遞歸復(fù)制
if (prop && typeof prop === "object") {
obj2[i] = deepCopy(prop);
} else {
// 如果是基本數(shù)據(jù)類型,只是簡單的復(fù)制
obj2[i] = prop;
}
}
}
}
return obj2;
}
改進后的深拷貝函數(shù)如下:
//2-升級版
// 可處理特殊類型 Date 堂鲜、RegExp
// 可處理存在循環(huán)引用的對象栈雳,如 obj.a=obj
// 不足:無法深拷貝對象的函數(shù)屬性(可用eval處理箭頭函數(shù),但處理不了一般函數(shù)缔莲,以下代碼沒實現(xiàn))
// 實際中哥纫,可以用 lodash 庫提供的 cloneDeep 來實現(xiàn)深拷貝,不過它也未實現(xiàn)對函數(shù)深拷貝痴奏,
// An empty object is returned for uncloneable values such as error objects, functions, DOM nodes, and WeakMaps.
// https://github.com/lodash/lodash/blob/master/.internal/baseClone.js
function deepCopy2(obj,weakmap=new WeakMap()) {
if(weakmap.has(obj)){ return weakmap.get(obj)}
if(obj===null){ return null;}
if(obj instanceof RegExp){
return new RegExp(obj);
}
if(obj instanceof Date){
return new Date(obj);
}
//基本數(shù)據(jù)類型或者函數(shù)蛀骇,直接返回
//這樣處理函數(shù)就不是深拷貝了
const type=typeof obj;
if(type !=='object'){
return obj;
}
/**
* 如果obj是數(shù)組,那么 obj.constructor 是 [Function: Array]
* 如果obj是對象读拆,那么 obj.constructor 是 [Function: Object]
*/
let cons=obj.constructor;
let res=new obj.constructor();
// 當對象的一個屬性引用自身時擅憔,要避免相互引用造成死循環(huán),如obj1.a=obj1
// 可使用 WeakMap 來存儲這個引用自身的屬性
weakmap.set(obj,res);
// 拷貝對象上的可遍歷屬性
for(let key in obj){
res[key]=deepCopy2(obj[key],weakmap);
}
return res;
}
改進后的深拷貝函數(shù)基本上可以滿足使用要求檐晕。注意其中幾點:
(1)借助一個 WeakMap
結(jié)構(gòu) weakmap暑诸,存儲已復(fù)制的對象,這樣在復(fù)制引用自身的屬性時辟灰,直接返回 weakmap 中已有的對象个榕,從而避免陷入死循環(huán)。(深拷貝存在遞歸復(fù)制芥喇,存在函數(shù)嵌套西采,所以使用 WeakMap 而不是 Map —— WeakMap 的鍵名所指向的對象,不計入垃圾回收機制继控。)
(2)特殊類型 Date 械馆、RegExp,如果直接通過以下兩行代碼復(fù)制湿诊, Date類型屬性值是代碼執(zhí)行時的時間狱杰,而不是原始對象對應(yīng)屬性值;對于 RegExp類型屬性值厅须,結(jié)果就是根本不符仿畸。所以對這兩類特殊類型值,需要做特殊處理朗和。
let cons=obj.constructor;
let res=new obj.constructor();
(3)通過上面這兩行代碼错沽,保證拷貝后的對象類型與原始對象類型保持一致。
(4)諸如 error objects, functions, DOM nodes, and WeakMaps
這些值眶拉,是不可拷貝的千埃。
最后,關(guān)于深淺拷貝的詳細介紹忆植,可看這篇文章放可。