目錄
- 1. 簡介
- 2. API簡介
- 3. 基本使用
- 4. 拷貝函式
- 5. 指定拷貝深度
- 6. 回圈參考
- 7. 保持型別資訊
- 8. 拷貝不可列舉的屬性
- 9. 自定義拷貝規則
- 9.1. typeCopyers
- 9.1.1. Types
- 9.1.2. 拷貝者Copyer
- 9.2. 預設presetTypeCopierMap
- 9.3. 拷貝者的優先級
- 9.4. getCopy
- 9.1. typeCopyers
- 10. 創建帶預設拷貝規則的拷貝函式
內容
1. 簡介
deep-copy 是一個深拷貝工具,可對任意資料進行深度拷貝,包括 函式 function、正則 RegExp、Map、Set、Date、Array、URL 等等;支持含回圈參考關系的物件的拷貝,并且不會丟失成員的參考關系資訊 和 型別資訊,支持擴展,可根據資料型別定制拷貝邏輯,也可指定拷貝深度;所以,通過它可實作對任意型別的資料進行任意想要的拷貝;
具有以下特性:
- 支持物件成員回圈參考

- 拷貝后不會丟失型別資訊 和 成員參考關系資訊
- 可指定拷貝深度
- 即能拷貝可列舉的成員,也可拷貝不可列舉的成員
- 可拷貝函式
- 可根據型別自定義拷貝規則
- 支持預設拷貝規則
- 支持創建多個不同預設拷貝規則的拷貝函式
詳情請看:
- deep-copy的介紹與安裝
- 主頁:https://github.com/GuoBinyong/deep-copy
- [GitHub倉庫][]
- [碼云倉庫][]
如果您在使用的程序中遇到了問題,或者有好的建議和想法,您都可以通過以下方式聯系我,期待與您的交流:
- 給該倉庫提交 [issues][]
- 給我 Pull requests
2. API簡介
deep-copy 匯出了兩個工具函式
import {deepCopy,createDeepCopy} from "deep-copy"
-
deepCopy<V>(value:V,options?:DeepCopyOptions|null|undefined,typeCopyers?:TypeRevivers<Copier>|null|undefined):V:對 value 進行深拷貝操作,并將深拷貝后的值回傳;引數如下:value: any:被拷貝的值;options?:DeepCopyOptions|null|undefined:描述拷貝配置的選項物件;typeCopyers?:TypeRevivers<Copier>|null|undefined:可根據不同型別定制拷貝邏輯的配置物件
-
createDeepCopy(presetTypeCopierMap?:TypeReviverMap<Copier>):DeepCopy:創建并回傳帶有默認 typeCopyers 的deepCopy()函式;引數如下:presetTypeCopierMap?:TypeReviverMap<Copier>:默認的 typeCopyers;
3. 基本使用
deep-copy 庫中 會匯出 deepCopy() 函式,用它可以直接對值進行深拷貝,如下所示:
import {deepCopy} from "../dist/deep-copy.es"
let value = {
name:"root",
sub:{
name:"member1",
fun:function () {
console.log("這是函式")
}
}
};
let copy = deepCopy(value);
其中 copy 和 value 的資訊結構是完全一樣的,但 copy 和 value 及其成員(除了函式(方法)外)已經是完全兩份實體,占據著兩份不同的記憶體空間;
4. 拷貝函式
默認情況下,deepCopy() 不會對 函式物件 進行深拷貝,比如上例中的 value.sub.fun,所以,copy 中的函式(方法) 和 value 中的函式 是同一函式,占據同一份記憶體空間,即 copy.sub.fun === value.sub.fun;
如果希望 deepCopy() 對函式也進行深拷貝,可以給 deepCopy() 傳遞第2個引數并設定選項 copyFun 為 true,如下所示:
let copy = deepCopy(value,{
copyFun:true
});
些時,copy 中的函式(方法) 和 value 中的函式 具有相同的代碼邏輯,但已經是兩個不同的函式實體,占據不同的記憶體空間,即 copy.sub.fun !== value.sub.fun;
5. 指定拷貝深度
如果你只需要實體進行一定深度的深拷貝,比如:只對實體、實體的成員、實體的成員的成員 進行拷貝操作,再深入的不進行拷貝操作,像這樣的需求,可以通過給 deepCopy 傳遞 maxDepth 選項來實作,如下:
let copy = deepCopy(value,{
maxDepth:2
});
maxDepth 是可選的,默認值為:Infinity;表示拷貝的最大深度;當值為 undefined 或 null 時,會使用默認值,表示無限深度;被拷貝的值(本例中是 value)本身的深度為 0 ,被拷貝值的成員的深度為 1 ,依次類推;
6. 回圈參考
當物件 與 物件 之間 或 物件 與 成員 之前 存在類似以下參考關系時,就會形成回圈參考

JSON的深拷貝方案是無法處理回圈參考關系 let copy = JSON.parse(JSON.stringify(value)),會造成記憶體溢位;
deepCopy() 支持拷貝這種帶回圈參考關系的物件,并且拷貝后的值,仍然會保持這種回圈參考關系,如下所示:
let root = {
name:"root"
};
let member1 = {
name:"member1",
fun:function () {
console.log("這是函式")
}
};
let member2 = {
name:"member2",
};
root.sub = member1;
member1.sub = member2;
member2.sub = root;
let rootCopy = DC.deepCopy(root);
拷貝前 和 拷貝后的 參考關系保持一樣:
root.sub.sub.sub === root; // true
rootCopy.sub.sub.sub === rootCopy; // true
7. 保持型別資訊
有些時候,被拷貝的物件并不是普通的物件,它可能是某個類的實體,比如:
class Person {
constructor(){
this.name = "";
this.email = "";
}
}
let p = new Person();
p.name = "郭斌勇";
p.email = "guobinyong@qq.com";
p 是 類 Person 的實體,但通過 JSON方案拷貝后
let pCopy = JSON.parse(JSON.stringify(p));
pCopy instanceof Person; // false
pCopy instanceof Object; // true
pCopy 卻變成了普通物件,即 Object 型別的實體;
然而,deepCopy() 會保持這種型別資訊,如下:
let pCopy = deepCopy(p);
pCopy instanceof Person; // true
拷貝后的 pCopy 仍然是 Person 型別的實體;
注意:
deep-copy 在拷貝實體時,會用實體所屬的類創建一個新的實體,創建時并不會給建構式傳遞任何引數, 然后將原來實體本身的成員的副本重設 新實體為新實體的成員;這對于那些需要給建構式傳遞引數的類 可能會存在問題;如果某類創建實體時依賴建構式的引數,則您可以針對些類定制拷貝規則;
8. 拷貝不可列舉的屬性
deepCopy() 默認只拷貝物件自身的可列舉屬性,如果您也想拷貝物件自身的不可列舉的屬性,則可以給 deepCopy() 傳遞 allOwnProps 選項,并設定為 true,如下:
let copy = deepCopy(value,{
allOwnProps:true
});
allOwnProps?:boolean | undefined | null:可選選項;默認值:undefined; 表示是否要拷貝所有自身的屬性,包不可列舉的,但不包括原型鏈上的屬性;true:拷貝物件自身(不包括原型上的)的所有屬性(包括不可列舉的);false|undefined|null: 只拷貝物件自身中(不包括原型上的)可列舉的屬性;
9. 自定義拷貝規則
對于下面這個 Person 類,在創建實體時(比如:p)需要傳入一個構造引數 sex,這個引數決定了模型的構建程序,如下:
class Person {
constructor(sex){
this.sex = sex;
// 根據 sex 初始化模型
switch(sex){
case "男":{
console.log("初始化男性特性");
break;
}
case "女":{
console.log("初始化女性特性");
break;
}
default:{
console.log("初始化人妖特性");
}
}
this.name = "";
this.email = "";
this.idCard = null; //身份證,是一個物件
this.mate = null; //配偶,也是 Person 類的實體
}
}
let p = new Person("男");
p.name = "郭斌勇";
p.email = "guobinyong@qq.com";
p.idCard = {
id:4231232776886677, //身份
address:"中國的一個小農村里"
};
//設定配偶
p.mate = new Person("女");
p.mate.name = "簡愛";
像這種型別的類,如果使用 deepCopy() 的默認拷貝邏輯的話,新拷貝的 Person 實體可能會執行錯誤的構建程序,因為 deepCopy() 在創建新實體時,不會傳入任何構建引數;像這種型別,我們就需要提供自定義的拷貝規則了;
如果你想自定義某個型別實體的深拷貝規則,有以下幾種方案:
- 給
deepCopy()函式提供typeCopyers引數 或 設定presetTypeCopierMap預設; - 給實體增加
getCopy方法;
9.1. typeCopyers
可以通過 typeCopyers 引數自定義拷貝規則,如下:
let pCopy = deepCopy(p,null,{
// 當需要拷貝 Person 型別的實體時,會回呼這個函式,這個函式需要回傳 Person 實體的副本;這個函式稱為 拷貝者 Copier
"Person": function(value,copyMember,options){
let copy = new Person(value.sex); // 創建新的 Person 實體,作為 value 的副本
// 給副本 copy 設定基本型別的成員;
copy.name = value.name;
copy.email = value.email;
// 如果參考型別的成員也需要深拷貝,則需要通過 copyMember() 函式拷貝 參考型別的成員
copy.idCard = copyMember(value.idCard);
/**
* copyMember() 函式會回傳 拷貝后的值;
* 但如果被拷貝的值存在回圈參考的話,copyMember() 會回傳 undefined;這種情況下,可通過給 copyMember() 傳遞回呼函式(第2個引數),通過回呼函式來獲取拷貝后的值;
* 無論是否存在回圈參考,通過回呼函式總能拿到拷貝后的值,回呼函式是一種保險的方法
*/
copyMember(value.mate,function(mateCopy){
copy.mate = mateCopy;
});
// 需要將副本回傳;
return copy;
}
});
deepCopy() 函式的 typeCopyers 引數是 TypeRevivers<Copier> 型別的,是用來描述 型別 和 拷貝者 Copier(提供拷貝實體回呼函式)的對應關系的,上例中 typeCopyers 使用的是 物件形式,它共以下幾種描述方式:
{[TypeName:ExactTypeName]:Copyer}:物件形式,屬性名字 是 型別的名字,屬性值是型別的 拷貝者(Copyer);[Types, Copyer][]:陣列形式,陣列中的元素是[Types, Copyer]型別的元組,元組的第一個元素是Types,第二個元素是 拷貝者;Map<Types, Copyer>:Map形式,Map中的鍵是Types型別,值是 拷貝者;
9.1.1. Types
Types 是 ExactTypeName、ExactType 和 其陣列結構的聯合,定義如下:
type Types = DataType | DataTypeArray;
type DataType = ExactTypeName | ExactType;
type DataTypeArray = DataType[];
-
ExactType:精確型別,可更加細致地描述型別;各種型別的值(左側) 與ExactType(右側) 的映射如下:undefined:undefinednull:nullstring:"string"number:"number"bigint:"bigint"boolean:"boolean"symbol:"symbol"- 普通函式 :
Function - 異步函式 :
AsyncFunction - 生成器函式 :
GeneratorFunction - 沒有原型的物件(如:通過
Object.create(null)創建的物件) :"object" - 其它任何型別的實體 : 該實體的建構式
-
ExactTypeName:精確型別的字串表示;各種型別的值(左側) 與ExactTypeName(右側) 的映射如下:undefined:"undefined"null:"null"string:"string"number:"number"bigint:"bigint"boolean:"boolean"symbol:"symbol"- 普通函式 :
"Function" - 異步函式 :
"AsyncFunction" - 生成器函式 :
"GeneratorFunction" - 沒有原型的物件(如:通過
Object.create(null)創建的物件) :"object" - 其它任何型別的實體 : 該實體的建構式的名字
所以,Types 可以是具體的型別 ExactType ,比如:Person 類;也可以是 型別的名字字串 ExactTypeName ,比如:'Person';或者是包含多個型別的陣列,比如:[Person,'Map',Set];
9.1.2. 拷貝者Copyer
拷貝者 Copyer 是一個型別為 (value:T,copyMember:CopyMember,options:CopierOptions)=>T 函式,嚴格的定義如下:
type Copier<T = any,Host = any> = (this:T,value:T,copyMember:CopyMember,options:CopierOptions<Host>)=>T
當需要拷貝某個型別的實體時,就會呼叫這個型別的 拷貝者 Copyer ,然后將拷貝者回傳的值作為那個實體的副本;
在呼叫拷貝者 Copyer 時,會將 Copyer 的 this 設定為被拷貝的值(與 value 引數相同的值),并會給 Copyer 傳遞以下引數:
value:被拷貝的值;copyMember:用于深拷貝value的成員的深拷貝函式;型別為(member:T,completeCB?:CompleteCB<T>|null|undefined,key?:K,host?:H | null | undefined,options?:CopyMemberOptions)=>T|undefined;拷貝后副本會通過copyMember()的回傳值(當被拷貝的成員不存在回圈參考時) 和completeCB回呼函式 回傳;options:包含了其它資訊的物件{ allOwnProps:boolean; key:any; host:Host; type:string; depth:number; copyFun:boolean; }
拷貝者 Copyer 需要將自定創建的 value 的副本回傳;
在創建 value 的副本的程序中,如果需要對 value 的參考型別的成員 進行拷貝,則需要呼叫 copyMember(value) 函式,這個函式是專門用于 深拷貝 value 的成員的;拷貝后副本會通過 copyMember(value) 的回傳值(當被拷貝的成員不存在回圈參考時) 和 傳給 copyMember(value,completeCB) 的 completeCB 回呼函式 傳回;例外的情況是:如果 value 的某個成員存在回圈參考,則 copyMember(value) 會回傳 undefined,這種情況下,只能通過 completeCB 回呼函式 傳回拷貝后的值;
9.2. 預設presetTypeCopierMap
如果經常需要給 deepCopy() 傳遞包含相同 型別和拷貝者 的 typeCopyers 引數,則可以將那些經常用到的 拷貝者 設定到 deepCopy() 的預設 deepCopy.presetTypeCopierMap 中;
deepCopy 函式物件有個屬性 presetTypeCopierMap 是用來設定常用的拷貝者的,它的型別是 Map<Types, Reviver>,所以上面的例子也可以如下設定預設:
// 當需要拷貝 Person 型別的實體時,會回呼些函式
deepCopy.presetTypeCopierMap.set(Person,function(value,copyMember,options){
let copy = new Person(value.sex); // 創建新的 Person 實體,作為 value 的副本
// 給副本 copy 設定基本型別的成員;
copy.name = value.name;
copy.email = value.email;
// 如果參考型別的成員也需要深拷貝,則需要通過 copyMember() 函式拷貝 參考型別的成員
copy.idCard = copyMember(value.idCard);
/**
* copyMember() 函式會回傳 拷貝后的值;
* 但如果被拷貝的值存在回圈參考的話,copyMember() 會回傳 undefined;這種情況下,可通過給 copyMember() 傳遞回呼函式(第2個引數),通過回呼函式來獲取拷貝后的值;
* 無論是否存在回圈參考,通過回呼函式總能拿到拷貝后的值,回呼函式是一種保險的方法
*/
copyMember(value.mate,function(mateCopy){
copy.mate = mateCopy;
});
// 需要將副本回傳;
return copy;
})
以后使用 deepCopy() 時,就不需要傳遞 Person 型別的拷貝者了;
9.3. 拷貝者的優先級
當需要拷貝某個型別的實體時,deepCopy() 會通過以下幾種方法獲取該實體的副本:
- 首先使用
typeCopyers引數中的拷貝者; - 如果沒有找到拷貝者,則再使用預設
presetTypeCopierMap中的拷貝者; - 如果沒有找到拷貝者,則會將實體的
getCopy方法作為拷貝者; - 如果實體沒有
getCopy方法,則會使用typeCopyers引數中"default"對應的 拷貝者; - 如果沒有找到拷貝者,則會使用預設
presetTypeCopierMap中"default"對應的 拷貝者; - 如果沒有找到拷貝者,則會使用內置的默認拷貝邏輯;
其中,"default" 對應的拷貝者稱為 默認的拷貝者;
9.4. getCopy
通過上文的 [拷貝者的優先級][] 可以知道,我們也可以給物件增加 getCopy 方法 來自定義 該物件的拷貝邏輯,如果您想將拷貝者應用到某個型別的所有實體上,則只需要給該型別增加一個實體方法 getCopy 即可;
getCopy 方法的 和 拷貝者 Copier 的型別一樣,都是型別為 (value:T,copyMember:CopyMember,options:CopierOptions)=>T 函式;
所以,上例中,也可以如下自定義 實體 p 的拷貝邏輯:
p.getCopy = function(value,copyMember,options){
let copy = new Person(value.sex); // 創建新的 Person 實體,作為 value 的副本
// 給副本 copy 設定基本型別的成員;
copy.name = value.name;
copy.email = value.email;
// 如果參考型別的成員也需要深拷貝,則需要通過 copyMember() 函式拷貝 參考型別的成員
copy.idCard = copyMember(value.idCard);
/**
* copyMember() 函式會回傳 拷貝后的值;
* 但如果被拷貝的值存在回圈參考的話,copyMember() 會回傳 undefined;這種情況下,可通過給 copyMember() 傳遞回呼函式(第2個引數),通過回呼函式來獲取拷貝后的值;
* 無論是否存在回圈參考,通過回呼函式總能拿到拷貝后的值,回呼函式是一種保險的方法
*/
copyMember(value.mate,function(mateCopy){
copy.mate = mateCopy;
});
// 需要將副本回傳;
return copy;
};
或,如下自定義 Person 型別 的拷貝邏輯:
Person.prototype.getCopy = function(value,copyMember,options){
let copy = new Person(value.sex); // 創建新的 Person 實體,作為 value 的副本
// 給副本 copy 設定基本型別的成員;
copy.name = value.name;
copy.email = value.email;
// 如果參考型別的成員也需要深拷貝,則需要通過 copyMember() 函式拷貝 參考型別的成員
copy.idCard = copyMember(value.idCard);
/**
* copyMember() 函式會回傳 拷貝后的值;
* 但如果被拷貝的值存在回圈參考的話,copyMember() 會回傳 undefined;這種情況下,可通過給 copyMember() 傳遞回呼函式(第2個引數),通過回呼函式來獲取拷貝后的值;
* 無論是否存在回圈參考,通過回呼函式總能拿到拷貝后的值,回呼函式是一種保險的方法
*/
copyMember(value.mate,function(mateCopy){
copy.mate = mateCopy;
});
// 需要將副本回傳;
return copy;
};
10. 創建帶預設拷貝規則的拷貝函式
如果您在使用 deepCopy() 時,經常需要使用不同的預設,那您可能需要使用 createDeepCopy(presetTypeCopierMap) 來創建一個帶有預設 presetTypeCopierMap 的另一個新的 deepCopy() 函式;
createDeepCopy(presetTypeCopierMap) 方法會創建一個新的深拷貝函式,這個新的深拷貝函式 和 deepCopy() 具有相同的功能,只是 不同的物件,且預設為傳給 createDeepCopy(presetTypeCopierMap) 函式的 presetTypeCopierMap 引數,如下:
const presetTypeCopierMap = new Map();
presetTypeCopierMap.set(Person,function(value,copyMember,options){
let copy = new Person(value.sex); // 創建新的 Person 實體,作為 value 的副本
// 給副本 copy 設定基本型別的成員;
copy.name = value.name;
copy.email = value.email;
// 如果參考型別的成員也需要深拷貝,則需要通過 copyMember() 函式拷貝 參考型別的成員
copy.idCard = copyMember(value.idCard);
/**
* copyMember() 函式會回傳 拷貝后的值;
* 但如果被拷貝的值存在回圈參考的話,copyMember() 會回傳 undefined;這種情況下,可通過給 copyMember() 傳遞回呼函式(第2個引數),通過回呼函式來獲取拷貝后的值;
* 無論是否存在回圈參考,通過回呼函式總能拿到拷貝后的值,回呼函式是一種保險的方法
*/
copyMember(value.mate,function(mateCopy){
copy.mate = mateCopy;
});
// 需要將副本回傳;
return copy;
});
// deepCopy2 為新的深拷貝函式,功能和 deepCopy 一樣;
const deepCopy2 = createDeepCopy(presetTypeCopierMap);
其中 deepCopy2 為新的深拷貝函式,功能和 deepCopy 一樣;通過 deepCopy2 來進行深拷貝操作,如下:
let copy = deepCopy2(value);
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/263459.html
標籤:其他
