本文 AI 產出,尚未審核

JavaScript – 物件(Objects)

主題:Object.assign()


簡介

在 JavaScript 中,物件是最常見、也是最彈性的資料結構。開發者常常需要在執行 複製合併淺層覆寫 時,快速且安全地處理多個物件。這時候 Object.assign() 便是官方提供的標準工具,它讓我們可以在一行程式碼內完成「把來源物件的屬性複製到目標物件」的工作。

Object.assign() 不僅是寫程式時的便利函式,更是許多框架(如 React、Vue)在實作 immutable update預設值合併深層設定展開 時的基礎。了解它的運作原理、使用時機與限制,對於寫出可維護、效能良好的程式碼相當重要。


核心概念

1. 基本語法

Object.assign(target, ...sources);
  • target:欲被寫入的目標物件。若傳入非物件(如 nullundefined)會拋出 TypeError。
  • sources:一或多個來源物件。來源物件的 可列舉 (enumerable) 自有屬性 會依序複製到 target

注意Object.assign() 只會進行 淺層 複製,若屬性值本身是物件或陣列,複製的仍是參考


2. 淺層複製的特性

const src = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, src);

copy.a = 10;          // 不影響 src.a
copy.b.c = 20;        // 會同時改變 src.b.c,因為 b 是同一個物件的參考
  • 原始值 (primitive)(Number、String、Boolean、Symbol、BigInt)會被直接複製。
  • 物件、陣列、函式 等引用型別只會複製 參考,因此在修改時要特別留意。

3. 多來源物件的合併順序

Object.assign() 會依照 來源物件的順序 依次覆寫目標物件的屬性,最後一個來源的同名屬性會「勝出」。

const defaults = { host: 'localhost', port: 80, protocol: 'http' };
const env = { port: 443, protocol: 'https' };
const config = Object.assign({}, defaults, env);
// config => { host: 'localhost', port: 443, protocol: 'https' }

此特性常被用來 設定預設值,讓使用者提供的設定自動覆寫預設項目。


4. 只會複製可列舉的自有屬性

Object.assign() 不會複製:

  • 物件的 原型鏈 上的屬性。
  • 不可列舉 (enumerable: false) 的屬性。
  • 存取器屬性(getter / setter)會以 的形式複製,而非保留其存取器本身。
const source = Object.create({ inherited: 1 });
Object.defineProperty(source, 'hidden', {
  value: 2,
  enumerable: false
});
source.visible = 3;

const target = Object.assign({}, source);
console.log(target); // { visible: 3 },沒有 inherited 也沒有 hidden

5. 常見的實用範例

範例 1:淺層複製物件(避免直接引用)

const user = { name: 'Alice', role: 'admin' };
const clone = Object.assign({}, user);

clone.role = 'guest';
console.log(user.role); // 'admin' 仍保持不變

重點:使用空物件 {} 作為 target,即可得到一個新的物件實例。

範例 2:合併多個設定檔

const baseConfig = { theme: 'light', language: 'en' };
const userConfig = { language: 'zh-TW' };
const envConfig  = { debug: true };

const finalConfig = Object.assign({}, baseConfig, userConfig, envConfig);
// finalConfig => { theme: 'light', language: 'zh-TW', debug: true }

此寫法在 CLI 工具前端框架 中非常常見,讓不同層級的設定自然疊加。

範例 3:為函式新增預設參數(舊版寫法)

在 ES6 之前,我們常用 Object.assign() 為函式參數設定預設值:

function init(options) {
  const defaults = { width: 800, height: 600, color: 'white' };
  const settings = Object.assign({}, defaults, options);
  // 使用 settings 進行後續處理
}
init({ height: 400, color: 'black' });
// 設定結果 => { width: 800, height: 400, color: 'black' }

範例 4:快速複製陣列(利用「類陣列」物件)

const arr = [1, 2, 3];
const copy = Object.assign([], arr);
console.log(copy); // [1, 2, 3]

雖然 Array.prototype.slice() 或展開運算子 [...] 更直觀,但了解 Object.assign() 也能處理「類陣列」物件(如 arguments)是一個好技巧。

範例 5:將多個物件的屬性平鋪到單一物件(常用於 Redux reducer)

const actionA = { type: 'ADD_TODO', payload: { id: 1, text: 'Buy milk' } };
const actionB = { meta: { timestamp: Date.now() } };

const mergedAction = Object.assign({}, actionA, actionB);
// mergedAction => { type: 'ADD_TODO', payload: {...}, meta: {...} }

Redux 中,這種「合併」方式讓 reducer 能一次取得全部資訊。


常見陷阱與最佳實踐

陷阱 說明 解決方式
淺層複製導致共享子物件 複製後的物件仍指向同一個子物件,修改任一方會互相影響。 若需要深層複製,使用 structuredClone()(ES2021)或第三方函式(如 lodash.cloneDeep)。
來源為 nullundefined 直接傳入會拋出 TypeError 在呼叫前先確認 source != null,或使用 `Object.assign(target, source
不可列舉屬性遺失 某些 API(如 Object.defineProperty 設定 enumerable: false)的屬性不會被複製。 若必須保留這類屬性,手動使用 Object.getOwnPropertyDescriptors 再配合 Object.defineProperties
存取器被轉為資料屬性 getter / setter 會被執行一次,結果作為普通屬性寫入。 若要保留存取器,可使用 Object.getOwnPropertyDescriptors + Object.defineProperties
目標物件被意外改變 直接把已有物件作為 target 會改變原始物件。 常見的安全寫法是 Object.assign({}, src),確保原始資料不被污染。

推薦的寫法

// 安全的深層合併(使用 structuredClone)
function deepMerge(...objects) {
  const result = {};
  for (const obj of objects) {
    Object.assign(result, JSON.parse(JSON.stringify(obj)));
  }
  return result;
}

// ES2021 原生深層複製(適用於可序列化的資料)
const deepCopy = (obj) => structuredClone(obj);

實際應用場景

  1. 設定預設值
    在建立 UI 元件或 API 客戶端時,使用 Object.assign({}, defaults, userOptions) 讓使用者的設定自動覆寫預設值。

  2. Redux / Vuex 狀態合併
    reducer 中常以 return Object.assign({}, state, changes); 產生新的 state,避免直接修改舊的 state 物件。

  3. 模組化資料攤平
    從多個來源(如表單、URL query、localStorage)取得設定,最後一次性合併成單一設定物件傳給核心函式。

  4. 快速淺層複製
    在需要暫時改動物件屬性、但又不想影響原始資料時(例如在測試環境),使用 Object.assign({}, original)

  5. 伺服器端渲染(SSR)資料注入
    把伺服器端產生的資料與客戶端的預設資料合併,確保兩端的狀態一致。


總結

Object.assign()ES6 之後標準化的物件操作工具,提供 淺層 的屬性複製與合併功能。它的優點在於語法簡潔、支援多來源合併、以及與其他語言特性(如展開運算子)互補。使用時必須注意:

  • 只複製 可列舉的自有屬性
  • 淺層 複製會保留子物件的引用,必要時採用深層複製方案。
  • 輸入 null / undefined 會拋錯,務必做好前置檢查。

掌握這些概念後,你就能在 設定預設值、資料合併、狀態管理 等日常開發情境中,靈活運用 Object.assign(),寫出更乾淨、可維護的程式碼。祝你在 JavaScript 的世界裡玩得開心、寫得更好!