[Medium] 手寫 cloneDeep (深拷貝)
2024年3月8日
💎 加入 E+ 成長計畫 與超過 500+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源
題目描述
在 JavaScript 複製值時,當複製的是非原始型別 (primitive type) 的資料型別時,例如:物件(object)、陣列 (array) 等,會遇到淺拷貝 (shallow copy) 和深拷貝 (deep copy) 的差異。在面試時被很常會要你當場手寫深拷貝,也就是手寫 Lodash 常見的 cloneDeep
。
所謂的深拷貝是指在拷貝時,物件 A 與物件 B 不同,兩者在原型鏈上僅是結構相同,但其屬性實際的地址不同。在拷貝值時,有可能會遇到變數是多層的情境,例如是一個物件裡還有物件,深拷貝的定義會是每一層的值都不會共享址 (reference)。
具體來說,以 lodash 這個套件提供的效用函式為例,有分成 clone
和 cloneDeep
兩種不同效用函式,clone
只用於淺拷貝(第一層拷貝),但 cloneDeep
可用於深拷貝。下面的例子說明兩者的區別:
// lodash 的淺拷貝 clone
var objects = [{ a: 1 }, { b: 2 }];
var shallow = _.clone(objects);
console.log(objects === shallow); // false
console.log(shallow[0] === objects[0]); // true
// lodash 的深拷貝 cloneDeep
var objects = [{ a: 1 }, { b: 2 }];
var deep = _.cloneDeep(objects);
console.log(objects === deep); // false
console.log(deep[0] === objects[0]); // false
本題解答
以下是本題的解答,詳細解題思路可以在 E+ 成長計畫 看到。如果想練習更多題目,推薦可以到 GreatFrontEnd 上練習。
解法一
let objA = {
a: 1,
b: { c: 3 },
};
function cloneDeep(item) {
return JSON.parse(JSON.stringify(item));
}
let objB = deepCopy(objA);
console.log(objA === objB); // false
console.log(objA.b === objB.b); // false
解法二
let objA = {
a: 1,
b: { c: 3 },
};
let objB = structuredClone(objA);
console.log(objA === objB); // false
console.log(objA.b === objB.b); // false
解法三
function deepClone(obj, cache = new WeakMap()) {
if (cache.has(obj)) {
return cache.get(obj);
}
if (obj === null || typeof obj !== "object" || typeof value === "function") {
return obj;
}
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
const result = Array.isArray(obj)
? []
: Object.create(Object.getPrototypeOf(obj));
cache.set(obj, result);
for (const key of Reflect.ownKeys(obj)) {
const value = obj[key];
result[key] = deepClone(value, cache);
}
return result;
}