JavaScript – 物件(Objects)
主題:Object.assign()
簡介
在 JavaScript 中,物件是最常見、也是最彈性的資料結構。開發者常常需要在執行 複製、合併 或 淺層覆寫 時,快速且安全地處理多個物件。這時候 Object.assign() 便是官方提供的標準工具,它讓我們可以在一行程式碼內完成「把來源物件的屬性複製到目標物件」的工作。
Object.assign() 不僅是寫程式時的便利函式,更是許多框架(如 React、Vue)在實作 immutable update、預設值合併、深層設定展開 時的基礎。了解它的運作原理、使用時機與限制,對於寫出可維護、效能良好的程式碼相當重要。
核心概念
1. 基本語法
Object.assign(target, ...sources);
target:欲被寫入的目標物件。若傳入非物件(如null、undefined)會拋出 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)。 |
來源為 null 或 undefined |
直接傳入會拋出 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);
實際應用場景
設定預設值
在建立 UI 元件或 API 客戶端時,使用Object.assign({}, defaults, userOptions)讓使用者的設定自動覆寫預設值。Redux / Vuex 狀態合併
reducer 中常以return Object.assign({}, state, changes);產生新的 state,避免直接修改舊的 state 物件。模組化資料攤平
從多個來源(如表單、URL query、localStorage)取得設定,最後一次性合併成單一設定物件傳給核心函式。快速淺層複製
在需要暫時改動物件屬性、但又不想影響原始資料時(例如在測試環境),使用Object.assign({}, original)。伺服器端渲染(SSR)資料注入
把伺服器端產生的資料與客戶端的預設資料合併,確保兩端的狀態一致。
總結
Object.assign() 是 ES6 之後標準化的物件操作工具,提供 淺層 的屬性複製與合併功能。它的優點在於語法簡潔、支援多來源合併、以及與其他語言特性(如展開運算子)互補。使用時必須注意:
- 只複製 可列舉的自有屬性。
- 淺層 複製會保留子物件的引用,必要時採用深層複製方案。
- 輸入
null/undefined會拋錯,務必做好前置檢查。
掌握這些概念後,你就能在 設定預設值、資料合併、狀態管理 等日常開發情境中,靈活運用 Object.assign(),寫出更乾淨、可維護的程式碼。祝你在 JavaScript 的世界裡玩得開心、寫得更好!