Deep Clone
為什麼需要深拷貝?
深拷貝在 JavaScript 裡是一種技術或方法,用來建立物件的完整複製,也就是說不僅複製物件本身,還包括它所有的屬性和子屬性。這樣做的目的是讓複製出來的物件跟原本的物件完全獨立,這樣對複製品的修改就不會影響到原本的物件。
如何實現深拷貝?
JSON.parse(JSON.stringify(obj))
在 JavaScript 裡,最簡單的深拷貝方法就是使用 JSON.parse(JSON.stringify(obj))
。
這個方法的原理是先把物件轉成 JSON 字串,然後再解析成一個新的物件。這樣可以確保大部分屬性都會被複製過去,但有一些限制,例如它無法複製 Function
、Set
、Date
、RegExp
、循環引用等。
const calendarEvent = {
title: "Meeting",
date: new Date(),
attendees: ["Steve"],
attendeesMap: new Map([["Steve"]]),
};
const deepCopy = (obj) => {
return JSON.parse(JSON.stringify(obj));
};
const copied = deepCopy(calendarEvent);
console.log(copied);
// date 變成字串,且無法保留原型
// attendeesMap 變成空物件,無法保留 Map
// {
// title: 'Meeting',
// date: '2024-08-19T01:25:25.283Z',
// attendees: [ 'Steve' ],
// attendeesMap: {}
// }
structuredClone
structuredClone
是 JavaScript 另一個用來做深拷貝的方法,它可以複製任何可序列化的物件,並支援一些特殊的類型,像是 Date
、Map
、Set
、ArrayBuffer
、Blob
、File
、RegExp
等,甚至連物件中的循環引用都可以處理。
const deepCopy = (obj) => {
return structuredClone(obj);
};
const calendarEvent = {
title: "Meeting",
date: new Date(),
attendees: ["Steve"],
attendeesMap: new Map([["Steve"]]),
};
const copied = deepCopy(calendarEvent);
console.log(copied);
// date 保持原型,且 Map 也被保留
// {
// title: 'Meeting',
// date: 2024-08-19T01:26:25.684Z,
// attendees: [ 'Steve' ],
// attendeesMap: Map(1) { 'Steve' => undefined }
// }
手動實現
有時候在面試中,可能會被要求手動實現深拷貝,這裡提供一個簡單的手動實現方法,能處理循環引用、Date
、RegExp
、Set
、Map
等特殊類型。
const deepCopy = (obj, hash = new WeakMap()) => {
if (Object(obj) !== obj) return obj; // 如果是 null、undefined、基本類型,直接回傳
if (hash.has(obj)) return hash.get(obj); // 如果已經處理過,直接回傳 (circular reference)
const result = Array.isArray(obj) ? [] : {};
hash.set(obj, result);
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Set) return new Set([...obj]);
if (obj instanceof Map) return new Map([...obj]);
// 使用 Reflect.ownKeys 遍歷所有屬性(包含 Symbal key),並遞迴處理
for (const key of Reflect.ownKeys(obj)) {
// Object.prototype.hasOwnProperty.call(obj, key) 用來過濾原型鏈上的屬性,只處理物件的自身屬性
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = deepCopy(obj[key], hash);
}
}
return result;
};
參考資料
- [Deep Cloning Objects in JavaScript, the Modern Way] (https://www.builder.io/blog/structured-clone)