JavaScript 中的拷貝分為兩種:淺拷貝和深拷貝,
一、淺拷貝
淺拷貝是指在拷貝程序中,只拷貝一個物件中的指標,而不拷貝實際的資料,所以,淺拷貝中修改新物件中的資料時,原物件中的資料也會被改變,
JavaScript 中淺拷貝可以通過如下幾種方式實作:
- 使用結構賦值的方式,例如
let newObject = {...oldObject} - 使用
Object.assign()方法,例如let newObject = Object.assign({}, oldObject)
二、深拷貝
深拷貝是指在拷貝程序中,拷貝一個物件中的所有資料,并創建一個新物件,對新物件進行操作并不會影響到原物件,
1、常規場景
JavaScript 中深拷貝可以通過如下幾種方式實作:
- 使用
JSON.parse(JSON.stringify(object))方法
需要注意的是:該方法會忽略 undefined 以及正則運算式型別的屬性,
const A = { a: 7788, b: undefined, c: new RegExp(/-/ig) },
B = JSON.parse(JSON.stringify(A));
console.log('A', A);
console.log('B', B);

- 使用遞回的方式,手動拷貝物件的每一層
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy;
if (Array.isArray(obj)) {
copy = [];
for (let i = 0; i < obj.length; i++) {
copy[i] = deepCopy(obj[i]);
}
} else {
copy = {};
for (let key in obj) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
const objA = { a: 123 },
objB = { b: 456 };
// 淺拷貝
const objC = {...objA};
console.log('objA.a', objA.a); // objA.a 123
console.log('objC.a', objA.a); // objC.a 123
objC.a = 788;
console.log('objA.a', objA.a); // objA.a 788
console.log('objC.a', objC.a); // objC.a 788
// 深拷貝
const objD = deepCopy(objB);
console.log('objB.b', objB.b); // objB.b 456
console.log('objD.b', objD.b); // objD.b 456
objD.b = 899;
console.log('objB.b', objB.b); // objB.b 456
console.log('objD.b', objD.b); // objD.b 899
這個函式接受一個引數 obj,如果它不是物件或者是 null,那么直接回傳該引數,如果它是陣列,則創建一個新陣列并遞回復制每一項,否則,創建一個新物件并遞回復制每一個屬性,
- 使用 lodash 類別庫的
_.cloneDeep函式、 underscore 中的_.clone()函式等第三方庫
2、特定場景一:內置物件型別的深拷貝
JavaScript 中復制內置物件型別(例如 Date,RegExp 等)的深拷貝可以使用特定的建構式來重新創建該物件,
例如,對于 Date 物件,可以使用 new Date(originalDate.getTime()) 來創建一個新的日期物件,其中 originalDate.getTime() 回傳原始日期物件的時間戳,
對于 RegExp 物件,可以使用 new RegExp(originalRegExp) 或 new RegExp(originalRegExp.source, originalRegExp.flags) 來創建一個新的正則運算式物件,
下面是一個使用建構式來復制內置物件型別的深拷貝示例:
function deepCopy(obj) {
let copiedObjects = new WeakMap();
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (copiedObjects.has(obj)) {
return copiedObjects.get(obj);
}
let copy;
if (obj instanceof Date) {
copy = new Date(obj.getTime());
} else if (obj instanceof RegExp) {
copy = new RegExp(obj);
} else if (Array.isArray(obj)) {
copy = [];
} else {
copy = {};
}
copiedObjects.set(obj, copy);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
copy[key] = deepCopy(obj[key], copiedObjects);
} else {
copy[key] = obj[key];
}
}
}
return copy;
}
這個示例的深拷貝函式首先檢查當前物件是否為內置物件型別,如果是,則使用相應的建構式重新創建該物件,否則創建一個普通物件或陣列,然后進行遞回復制每一個屬性,
需要注意的是,使用建構式復制內置物件型別只適用于部分內置物件型別,對于其他的內置物件型別,可能需要使用其他的方法來進行復制,或者使用第三方庫來進行復制,
總之,深拷貝復制內置物件型別需要考慮使用建構式來重新創建物件,如果需要對這些物件進行深拷貝操作,可以使用上述方法或其他庫來實作,
3、特定場景二:自定義物件型別的深拷貝
JavaScript 中自定義物件的深拷貝可以使用同樣的遞回方式實作,可以使用 WeakMap 方法,
function deepCopy(obj) {
let copiedObjects = new WeakMap();
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (copiedObjects.has(obj)) {
return copiedObjects.get(obj);
}
let copy;
if (obj instanceof MyCustomObject) {
copy = new MyCustomObject();
} else if (Array.isArray(obj)) {
copy = [];
} else {
copy = {};
}
copiedObjects.set(obj, copy);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
copy[key] = deepCopy(obj[key], copiedObjects);
} else {
copy[key] = obj[key];
}
}
}
return copy;
}
這個函式首先檢查當前物件是否為自定義物件,如果是,則創建一個新的自定義物件,否則創建一個普通物件或陣列,然后進行遞回復制每一個屬性,
注意,如果自定義物件中包含回圈參考,需要使用 WeakMap 來避免出現死回圈,
4、特定場景三:物件中存在函式或回圈參考
對于函式,通常會忽略它們,因為函式不能被復制,而是需要重新定義,
對于回圈參考,可以使用 WeakMap 來存盤已經復制過的物件,每次遇到回圈參考時,可以檢查 WeakMap 中是否已經有該物件的副本,如果有,則直接使用副本,而不是重新創建,
function deepCopy(obj) {
let copiedObjects = new WeakMap();
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (copiedObjects.has(obj)) {
return copiedObjects.get(obj);
}
let copy;
if (Array.isArray(obj)) {
copy = [];
} else {
copy = {};
}
copiedObjects.set(obj, copy);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
copy[key] = deepCopy(obj[key], copiedObjects);
} else {
copy[key] = obj[key];
}
}
}
return copy;
}
這是使用 WeakMap 的一種示例,這個示例的深拷貝函式遞回地復制物件,但檢查 WeakMap 中是否已經存在該物件的副本,如果存在則直接使用副本,而不是重新創建,
此外,使用 JSON.parse(JSON.stringify(obj)) 方法會自動忽略函式和回圈參考,但是會忽略 undefined 以及正則運算式型別的屬性,
還有,還可以使用第三方庫,如 lodash 中的 _.cloneDeep() 函式、 underscore 中的 _.clone() 函式等來實作物件中存在函式或回圈參考的深拷貝,
5、特定場景四:物件中有對其他物件的參考或者包含 Symbol 屬性的物件
對于物件中有對其他物件的參考,可以使用 WeakMap 來存盤已經復制過的物件,每次遇到對其他物件的參考時,可以檢查 WeakMap 中是否已經有該物件的副本,如果有,則直接使用副本,而不是重新創建,
對于物件中包含 Symbol 屬性的物件,可以使用 Object.getOwnPropertySymbols() 方法來獲取該物件所有的 Symbol 屬性,然后使用 Object.getOwnPropertyDescriptor() 方法來獲取這些 Symbol 屬性的值,最后將這些值賦給新物件,
function deepCopy(obj) {
let copiedObjects = new WeakMap();
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (copiedObjects.has(obj)) {
return copiedObjects.get(obj);
}
let copy;
if (Array.isArray(obj)) {
copy = [];
} else {
copy = {};
}
copiedObjects.set(obj, copy);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
copy[key] = deepCopy(obj[key], copiedObjects);
} else {
copy[key] = obj[key];
}
}
}
let symbols = Object.getOwnPropertySymbols(obj);
symbols.forEach(symbol => {
let descriptor = Object.getOwnPropertyDescriptor(obj, symbol);
Object.defineProperty(copy, symbol, descriptor);
});
return copy;
}
這是使用 WeakMap 和 Symbol 屬性的一種示例,這個示例的深拷貝函式首先檢查 WeakMap 中是否已經存在該物件的副本,如果存在則直接使用副本,而不是重新創建,然后使用 Object.getOwnPropertySymbols() 方法獲取該物件所有的 Symbol 屬性,最后使用 Object.getOwnPropertyDescriptor() 方法獲取這些 Symbol 屬性的值,并將這些值賦給新物件,
這種方法可以保證深拷貝物件中包含的所有屬性,包括對其他物件的參考和 Symbol 屬性,但還是不能復制內置物件型別,這些物件型別是不可列舉的,
三、回圈參考
JavaScript 中的回圈參考指的是兩個或多個物件之間相互參考的情況,這種情況通常發生在將一個物件賦給另一個物件的屬性時,同時還將另一個物件賦給第一個物件的屬性,
以下是一個示例:
let obj1 = {};
let obj2 = {};
obj1.prop = obj2;
obj2.prop = obj1;
這樣就會產生一個回圈參考,因為 obj1 和 obj2 相互參考,
回圈參考可能導致 JavaScript 引擎無法正確處理記憶體,并導致記憶體泄漏,因此,在撰寫 JavaScript 代碼時需要特別注意避免回圈參考,如果您需要在兩個物件之間建立關系,可以使用弱參考來避免回圈參考,
在深拷貝中遇到回圈參考就會導致死回圈,因此需要使用特殊的演算法來解決這個問題,可以使用遞回演算法和深度優先遍歷來實作深拷貝,在遍歷程序中跟蹤已經遍歷過的物件,如果遇到回圈參考就直接回傳已經遍歷過的物件的參考,
總的來說,在使用淺拷貝和深拷貝時,需要根據需求和物件的結構來進行選擇,通常來說,如果需要對物件進行修改并且不希望對原物件造成影響,那么應該使用深拷貝,如果只是需要讀取物件中的資料而不需要修改,那么可以使用淺拷貝,在實作深拷貝時,需要特別注意回圈參考和特殊屬性問題,
作者:yuzhihui出處:http://www.cnblogs.com/yuzhihui/ 宣告:歡迎任何形式的轉載,但請務必注明出處!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/542098.html
標籤:JavaScript
上一篇:node和npm如何升級版本
