一、概述
在JS開發中,我們常常需要複製或者克隆一個對象。對於簡單數據類型,如數字、字元串、布爾值等,這是很容易實現的。但是對於複雜數據類型,如對象或者數組,就需要用到JS的深拷貝。深拷貝是指將一個對象完全複製一份,並開闢一個新的內存空間存放新對象,新對象的改動不會影響原對象。相比之下,JS的淺拷貝只是複製對象的引用,並沒有開闢新的內存空間,因此改動新對象也會影響原對象。
二、基本實現方法
JS的深拷貝可以通過兩種方法實現。
1. JSON.parse()
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
通過JSON.stringify()將對象轉化為字元串,然後再通過JSON.parse()將字元串轉化為新的對象,新對象就是原對象的深拷貝。這種方法的優點是實現簡單,代碼量小,但是缺點也顯而易見:無法處理對象中含有函數、正則表達式等非JSON格式數據類型。而且該方法在處理大型對象的時候性能很低,因為需要進行兩次序列化和反序列化操作。
2. 遞歸
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj;
if (hash.has(obj)) return hash.get(obj);
let clonedObj = new obj.constructor();
hash.set(obj, clonedObj);
if (obj instanceof Map) {
obj.forEach((value, key) => {
clonedObj.set(deepClone(key, hash), deepClone(value, hash));
});
} else if (obj instanceof Set) {
obj.forEach(value => {
clonedObj.add(deepClone(value, hash));
});
} else {
Object.getOwnPropertyNames(obj).forEach(prop => {
if (Object.getOwnPropertyDescriptor(obj, prop).enumerable) {
clonedObj[prop] = deepClone(obj[prop], hash);
}
});
}
return clonedObj;
}
遞歸方法則不會存在以上的問題,可以處理複雜對象,但是要注意對象中是否包含自引用的情況,因此需要引入哈希表記憶化處理。
三、對象中含有函數的深拷貝
使用遞歸方式進行深拷貝的時候,如果對象中含有函數,就會出現深拷貝失誤的情況。因為遞歸過程中不會拷貝函數。因此需要單獨處理對象中的函數,這可以分為兩步:
1. 拷貝函數
let _toString = Object.prototype.toString;
let _hasOwnProperty = Object.prototype.hasOwnProperty;
function isFunction(obj) {
return _toString.call(obj) === '[object Function]' || typeof obj === 'function';
}
function cloneFunction(func) {
let bodyReg = /(?<={)(.|\n)+(?=})/m;
let paramReg = /(? param.trim());
return new Function(...params, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function copy(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj;
if (hash.has(obj)) return hash.get(obj);
let type = _toString.call(obj);
let copiedObj;
switch (type) {
case '[object Array]':
copiedObj = new obj.constructor();
break;
case '[object Function]':
return cloneFunction(obj);
case '[object RegExp]':
copiedObj = new RegExp(obj.source, obj.flags);
break;
case '[object Date]':
copiedObj = new Date(obj.getTime());
break;
case '[object Set]':
copiedObj = new Set();
break;
case '[object Map]':
copiedObj = new Map();
break;
default:
copiedObj = new obj.constructor();
}
hash.set(obj, copiedObj);
if (obj instanceof Map) {
obj.forEach((value, key) => {
copiedObj.set(copy(key, hash), copy(value, hash));
});
} else if (obj instanceof Set) {
obj.forEach(value => {
copiedObj.add(copy(value, hash));
});
} else {
for (let prop in obj) {
if (_hasOwnProperty.call(obj, prop)) {
if (type === '[object Array]' || type === '[object Object]') {
copiedObj[prop] = copy(obj[prop], hash);
} else {
Object.defineProperty(copiedObj, prop, Object.getOwnPropertyDescriptor(obj, prop));
}
}
}
}
return copiedObj;
}
上述代碼實現了函數的拷貝,其中cloneFunction()函數就是用於拷貝函數的。
2. 拷貝對象中的函數
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj;
if (hash.has(obj)) return hash.get(obj);
let clonedObj = copy(obj, hash);
hash.set(obj, clonedObj);
return clonedObj;
}
在進行遞歸遍歷後,調用copy()方法對對象進行拷貝,並記錄到哈希表中。這樣即可實現深拷貝。
四、應用場景
JS的深拷貝主要用於解決對象的克隆和數據格式轉化等問題。在以下場景中深拷貝會被廣泛應用:
1. React組件之間的通信
在React組件內部,有時需要將一個組件的狀態傳遞給子組件,這時候就需要使用深拷貝將狀態複製一份,以防止子組件改變父組件的狀態,從而避免數據污染的情況。
2. 前端緩存
在前端開發中,經常需要緩存一些數據到本地存儲中。如果數據源是一個對象,使用深拷貝可以保證數據不會在緩存的過程中被修改,從而避免數據完整性的問題。
3. 數據結構的複製
在JS中,集合類數據結構(例如Map,Set等)是常用的存儲數據的方式。在使用時,需要對這些數據結構進行複製,以便進行修改、刪除等操作。使用深拷貝可以確保複製後的數據和原數據完全獨立,防止由於互相引用導致結果不可預知。
4. 數據類型轉化
在開發中,有時需要將一個數據格式轉換成另一個格式。例如將一個JSON對象轉換為XML或CSV格式,可以通過先將JSON對象深拷貝得到一個JS對象,然後再將JS對象轉換為需要的格式。
五、總結
JS的深拷貝對於複製對象是一項非常關鍵的技術,掌握深拷貝的方法有助於提高代碼效率和數據完整性。本文介紹了兩種實現深拷貝的方法,以及如何處理對象中的函數,同時對深拷貝的應用場景作了介紹。
原創文章,作者:HUDVO,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/370459.html