Immutable update
Shallow clone 與 deep clone 的區別
首先要講到 Javascript 「物件傳址」的特性,在 Javascript 中物件在建立的時候會將記憶體的位址賦予至變數上,常常有多個變數共用同一個位址,所以 mutate 其中一個物件的屬性的內容時,其他的變數也會受到影響,就是因為他們共用同一個記憶體位址。
Shallow clone
Shallow clone 又稱淺複製,當想要將物件重新賦予至另外一個物件的時候又不想要修改其原始值,就可以使用 Shallow clone 將物件內第一層的屬性複製到另外一個 reference 上,這樣既原來的物件的 reference 不同,也可以避免修改到原始物件的屬性。
Shallow clone 的方法
spread 語法
const memberInfo = {
id: 120,
name: "Andy",
isVip: false,
birthday: "1996/01/12",
hobbies: ["Photography", "Cooking", "Painting"],
};
const copyBySpread = { ...memberInfo }; //使用 spread 語法複製物件 memberInfo
console.log("copyBySpread === memberInfo", copyBySpread === memberInfo); //false
//可以確定 copyBySpread 與 memberInfo 這兩個物件的 reference 不同的
console.log(
"copyBySpread.hobbies === memberInfo.hobbies",
copyBySpread.hobbies === memberInfo.hobbies,
); //true
//memberInfo 中的 hobbies 屬性是一個陣列,物件的比對 === 就是其記憶體的位址,
//可以確定 copyBySpread 與 memberInfo 這兩個物件的 hobbies 這個屬性都是指向同一個 reference 的,
//所以當修改 copyBySpread.hobbies 的內容時,也會影響到 memberInfo.hobbies 的內容。
那如果想要淺複製物件內巢狀的物件或物件可以這麼做:
const copyNestBySpread = {
...memberInfo, //先淺複製 memberInfo 第一層的屬性
hobbies: [...memberInfo.hobbies], //在 copyNestBySpread 物件中新增 hobbies 屬性,然後將 memberInfo.hobbies 陣列淺複製到 copyNestBySpread.hobbies
};
//這時候再次檢查 copyNestBySpread.hobbies 與 memberInfo.hobbies 的 reference 是否相同
console.log(
"copyNestBySpread.hobbies === memberInfo.hobbies",
copyNestBySpread.hobbies === memberInfo.hobbies,
); //false
// 這時候修改 copyNestBySpread.hobbies 的內容就不會影響到 memberInfo.hobbies 的內容
使用 spread 展開運算符的好處是語法相對簡潔,讓程式碼容易閱讀。
Object.assign()
const memberInfo = {
id: 120,
name: "Andy",
isVip: false,
birthday: "1996/01/12",
hobbies: ["Photography", "Cooking", "Painting"],
};
const copyByAssign = Object.assign({}, memberInfo); ////使用 Object.assign() 複製物件 memberInfo
console.log("copyByAssign === memberInfo", copyByAssign === memberInfo); //false
console.log(
"copyByAssign.hobbies === memberInfo.hobbies",
copyByAssign.hobbies === memberInfo.hobbies,
); //true
Deep clone
Deep clone 又稱深複製,相對淺複製僅是將物件的第一層複製,深複製則是將物件的所有層級都複製一份,深複製當遇到巢狀物件或是陣列時,就會進行深層的遍歷,將每一次層的值都進行複製,如此一來複製出來的物件當被修改時就不會影響到原來的物件。
Deep clone 的方法
JSON.parse(JSON.stringify(...))
const memberInfo = {
id: 120,
name: "Andy",
isVip: false,
birthday: "1996/01/12",
hobbies: ["Photography", "Cooking", "Painting"],
};
//先將 memberInfo 物件轉換成 JSON 字串,再將 JSON 字串轉換成物件
function deepCopy(item) {
return JSON.parse(JSON.stringify(item));
}
const deepCopyByJSON = deepCopy(memberInfo);
console.log("deepCopyByJSON === memberInfo", deepCopyByJSON === memberInfo); //false
console.log(
"deepCopyByJSON.hobbies === memberInfo.hobbies",
deepCopyByJSON.hobbies === memberInfo.hobbies,
); //false
但是使用 JSON.parse(JSON.stringify(...))
進行深複製時,如果物件內的屬性有不可以序列化的值,就會導致深複製失敗。ex: undefined
、function
、symbol
、BigInt
、Date
、RegExp
、Error
、Map
、Set
const memberInfo = {
id: 120,
name: "Andy",
isVip: false,
birthday: "1996/01/12",
hobbies: ["Photography", "Cooking", "Painting"],
getMoreInfo: function getMoreInfo() {
return null;
},
createdTime: new Date("2024-06-10"),
};
function deepCopy(item) {
return JSON.parse(JSON.stringify(item));
}
const deepCopyByJSON = deepCopy(memberInfo);
deepCopyByJSON.createdTime
跟memberInfo.createdTime
的值就不同了,因為Date
物件無法被序列化。
deepCopyByJSON.getMoreInfo
的值為 undefined,因為 function 也無法被序列化。