JavaScript 展開運算子的淺拷貝陷阱:物件參考問題解析
此文章是 FrontendMaster 上的 Advanced Web Development Quiz 課程筆記
題目
以下程式碼執行後,member2 會印出什麼結果?
javascript
const member = {
name: 'Jane',
address: { street: '101 Main St' },
};
const member2 = { ...member };
member.address.street = '102 Main St';
member.name = 'Sarah';
console.log(member2);[1] { name: "Jane", address: { street: "101 Main St" } }
[2] { name: "Jane", address: { street: "102 Main St" } }
[3] { name: "Sarah", address: { street: "101 Main St" } }
[4] { name: "Sarah", address: { street: "102 Main St" } }說明
這一題是在問 JavaScript 物件的淺複製與展開運算子的觀念
首先 { ...member } 這裡的 ... 的做的是淺複製,會把物件裡面的第一層的值直接複製放在一個新的物件裡面,如果第一層裡面的值如果是基本型別,那麼他將會完全被複製過去,與原本物件內的值無關,但如果是物件型別會變成只複製了參考(reference),而非物件本身。
也就是說 member 與 member2 裡面的 address 共享了同一個物件 { street: "101 Main St" }
如果要完全獨立兩個物件,要用深拷貝才可以達到
展開運算子(spread operator):用於複製物件時,只會進行淺拷貝(Shallow Copy)。
JavaScript 的資料型別
JavaScript 的資料型別分為兩大類:原始型別(Primitive Type) 和 物件型別(Object Type)。
- 基本型別 (Primitive Type)
- String(字串):文字資料,如 "Hello"
- Number(數字):數值,如 42、3.14
- Boolean(布林):true 或 false
- Null:表示「刻意的空值」
- Undefined:表示「未定義」
- Symbol:ES6 新增,用於建立唯一識別符
- BigInt:ES2020 新增,用於表示任意精度的整數
- 物件型別 (Object Type)
- Object(狹義物件):鍵值對集合,如
{ name: "Jane" } - Array(陣列):有序列表,如 `[1, 2, 3]``
- Function(函式)
- Date(日期)、RegExp(正規表示式)等內建物件
- Object(狹義物件):鍵值對集合,如
深拷貝
深拷貝會遞迴複製所有層級的屬性,確保新物件與原物件完全獨立,互不影響。
方法一 structuredClone()
這是現代瀏覽器提供的原生方法,推薦優先使用
javascript
const obj1 = {
name: 'Jane',
address: { street: '101 Main St' },
};
const obj2 = structuredClone(obj1);
obj1.address.street = '102 Main St';
console.log(obj2.address.street); // "101 Main St"限制:
- 無法複製 function,會丟出錯誤
javascript
structuredClone({ fn: () => {} }); // ERROR- 無法複製 DOM 節點,會丟出錯誤
javascript
structuredClone({ el: document.body }); // ERROR- 無法複製 Property descriptors, setters, and getters
javascript
structuredClone({
get foo() {
return 'bar';
},
}); // 變成: { foo: 'bar' }- 無法複製物件原型
javascript
class MyClass {
foo = 'bar';
myMethod() {
/* ... */
}
}
const myClass = new MyClass();
const cloned = structuredClone(myClass);
// 變成: { foo: 'bar' }
cloned instanceof myClass; // false方法二 JSON.parse(JSON.stringify())
快速但有限制的方法
javascript
const obj2 = JSON.parse(JSON.stringify(obj1));限制:
- 無法複製 函式(function)
- 會忽略 undefined 和 Symbol
- 無法處理循環參考
- Date 物件會變成字串
方法三 第三方套件
lodash 有 _.cloneDeep,多數情況下可以優先使用前面的方法,除非必須要在前面功能不被支援的情況下才使用這個做法
答案
[2]
