Skip to content

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),而非物件本身。

也就是說 membermember2 裡面的 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(正規表示式)等內建物件

深拷貝

深拷貝會遞迴複製所有層級的屬性,確保新物件與原物件完全獨立,互不影響。

方法一 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"

限制:

  1. 無法複製 function,會丟出錯誤
javascript
structuredClone({ fn: () => {} }); // ERROR
  1. 無法複製 DOM 節點,會丟出錯誤
javascript
structuredClone({ el: document.body }); // ERROR
  1. 無法複製 Property descriptors, setters, and getters
javascript
structuredClone({
    get foo() {
        return 'bar';
    },
}); // 變成: { foo: 'bar' }
  1. 無法複製物件原型
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]

參考

最後更新時間:

0 %
MIT Licensed | Copyright © 2025-present Wen-Hsiu's Blog