本文 AI 產出,尚未審核

JavaScript 物件單元 ── 結構化複製(structuredClone

簡介

在日常開發中,我們常常需要把一個物件或資料結構「完整」地複製一份,避免在原本的資料上不小心做出副作用。傳統的做法是使用 JSON.stringify / JSON.parseObject.assign 或是展開運算子(...),但這些方法都有明顯的限制:無法正確處理循環參照、Date、RegExp、Map、Set、Blob、File 等特殊類型

ECMAScript 2021(ES12)正式將 structuredClone 引入標準,讓開發者可以以原生且安全的方式,深度複製任何支援「結構化序列化」的值。它的行為與瀏覽器內建的 postMessage 所使用的序列化機制相同,且在 Node.js 17 以上也已支援。掌握 structuredClone,能讓你在資料搬移、狀態管理、測試 mock 等情境下,寫出更可靠且維護成本更低的程式碼。


核心概念

1. 什麼是「結構化複製」?

structuredClone(value, [options]) 會對 value 進行「結構化序列化」再反序列化,產生一個 深層且獨立 的副本。它支援的類型包括:

類型 支援情況
原始值(Number、String、Boolean、null、undefined、Symbol)
物件、陣列
Date、RegExp、Map、Set、ArrayBuffer、TypedArray
Blob、File、ImageBitmap、OffscreenCanvas
循環參照(Circular reference)
不支援:Function、Error(除非是 DOMException)、BigInt (在某些環境)、DOM Node(除非是 ImageBitmap

注意structuredClone 會拋出 DataCloneError(或 DOMException)如果傳入的值無法被序列化。

2. 基本語法

const copy = structuredClone(originalValue[, options]);
  • originalValue:要被複製的資料。
  • options(可選):目前只有 transfer 屬性,可用於「轉移」ArrayBufferMessagePortImageBitmap 等可轉移物件,類似 postMessage 的行為。
// 只轉移而不複製(原始物件會變成 neutered)
const [buffer, transferred] = structuredClone({ buf }, { transfer: [buf] });

3. 為什麼它比 JSON.parse(JSON.stringify(...)) 更好?

項目 JSON 方法 structuredClone
循環參照 ❌ 會拋錯
Date、RegExp 變成字串 ✅ 保留原型
Map、Set 失真 ✅ 完整保留
Binary(ArrayBuffer) 失真或需手動處理 ✅ 自動處理
性能 常因字串化而較慢 多數情況下更快(原生實作)

程式碼範例

範例 1:基本深層複製

const person = {
  name: 'Alice',
  hobbies: ['reading', 'hiking'],
  profile: {
    age: 28,
    created: new Date('1996-07-15')
  }
};

const clone = structuredClone(person);

// 修改 clone 不會影響原本
clone.profile.age = 30;
clone.hobbies.push('coding');

console.log(person.profile.age); // 28
console.log(person.hobbies);     // ['reading', 'hiking']

說明cloneperson 完全分離,即使是巢狀物件或陣列也不會共享參考。

範例 2:支援 Map、Set、RegExp

const data = {
  map: new Map([['key1', 1], ['key2', 2]]),
  set: new Set([1, 2, 3]),
  regex: /ab+c/gi
};

const copy = structuredClone(data);

console.log(copy.map instanceof Map);   // true
console.log(copy.set instanceof Set);   // true
console.log(copy.regex instanceof RegExp); // true

// 確認內容相同
console.log([...copy.map]); // [['key1',1],['key2',2]]
console.log([...copy.set]); // [1,2,3]

說明MapSetRegExp 都被正確保留其型別與內容。

範例 3:處理循環參照

const node = { name: 'node' };
node.self = node; // 循環參照

const clonedNode = structuredClone(node);
console.log(clonedNode === clonedNode.self); // true
console.log(clonedNode === node);            // false

說明:即使資料結構中出現自我參照,structuredClone 仍能正確重建相同的循環結構。

範例 4:轉移(Transfer)ArrayBuffer

// 建立一個 8 位元的緩衝區
const buffer = new Uint8Array([1, 2, 3, 4]).buffer;

// 使用 transfer 讓 buffer 直接被「搬移」而不是複製
const [transferred] = structuredClone({ buf: buffer }, { transfer: [buffer] });

console.log(buffer.byteLength);        // 0   (已被 neutered)
console.log(transferred.buf.byteLength); // 4

說明:在需要大量資料搬移(如 Web Worker)時,使用 transfer 可以避免不必要的記憶體拷貝。

範例 5:在 Node.js 中複製 Blob

// Node.js 18+ 才支援 Blob
const blob = new Blob(['Hello, world!'], { type: 'text/plain' });

(async () => {
  const clone = structuredClone(blob);
  const text = await clone.text();
  console.log(text); // "Hello, world!"
})();

說明Blob 也是可被 structuredClone 處理的物件,適用於檔案上傳、緩衝區傳遞等情境。


常見陷阱與最佳實踐

陷阱 描述 解決方式
Function 無法被克隆 structuredClone 會拋出 DataCloneError 若必須保留函式,改用手動複製或將函式抽離成模組、重新注入。
Error 物件會失真 Error 只會保留 message,其他屬性會遺失。 只在需要傳遞錯誤訊息時使用;若要完整保留,自行序列化(JSON.stringify(error, ...))或建立自訂錯誤類別。
BigInt 在舊環境不支援 某些瀏覽器或 Node 版本仍未支援 BigInt 的結構化克隆。 確認執行環境(if (typeof structuredClone === 'function')),或將 BigInt 轉成字串後再克隆。
轉移物件被「neutered」 使用 transfer 後,原始緩衝區會變成長度 0。 僅在確定不再使用原始緩衝區時才使用;若仍需保留,改用深層複製。
跨執行環境限制 Web Worker、Service Worker、Node Worker 之間的傳遞需要支援同樣的類型。 事先確認目標環境支援的類型清單,避免在不支援的環境拋錯。

最佳實踐

  1. 先檢查支援性

    if (typeof structuredClone !== 'function') {
      throw new Error('此瀏覽器不支援 structuredClone,請使用 polyfill 或其他方法。');
    }
    
  2. 盡量避免在熱路徑大量使用
    雖然 structuredClone 為原生實作,仍會涉及記憶體分配。對於每幀更新的遊戲或即時渲染,應慎用或先做效能測試。

  3. 配合 TypeScript 時明確型別

    const copy = structuredClone(original) as typeof original;
    
  4. 使用 transfer 以提升跨執行緒效能
    只在需要大量二進位資料(如音訊、影像)時使用,能顯著降低 GC 壓力。

  5. 在測試環境中利用 structuredClone 產生獨立測試資料
    透過深層複製避免測試案例之間互相污染。


實際應用場景

場景 為什麼選擇 structuredClone
Redux / Zustand 狀態快照 想要在「時間旅行除錯」時取得完整的狀態快照,且包含 MapSetDate 等非純 JSON 類型。
Web Worker 與主執行緒資料傳遞 直接使用 postMessage(data) 背後即是 structuredClone,若想先在主執行緒驗證資料,可手動呼叫 structuredClone
伺服器端渲染 (SSR) 中的資料深拷貝 在 Node.js 中,把從資料庫取得的物件傳遞給多個渲染流程時,避免共享同一參考。
單元測試的 mock 資料 測試函式是否會意外改變傳入的物件;使用 structuredClone 輕鬆產生不會被測試程式修改的基礎資料。
圖形或音訊處理的緩衝區搬移 大型 ArrayBufferAudioBuffer 在 Web Worker 中處理時,使用 transfer 讓資料「零拷貝」搬移。

總結

structuredClone 為 JavaScript 帶來了 安全、完整且原生的深層複製 能力。相較於傳統的 JSON 方案或手動遞迴複製,它不僅支援更廣泛的資料型別,還能正確處理循環參照與大型二進位緩衝區的「轉移」操作。對於 狀態管理、跨執行緒通訊、測試資料準備 等常見需求,structuredClone 都是值得優先考慮的工具。

在實務開發中,記得:

  • ✅ 確認執行環境支援(或提供 polyfill)
  • ✅ 留意不支援的類型(Function、Error 等)
  • ✅ 盡可能使用 transfer 以提升跨執行緒效能
  • ✅ 在效能敏感的路徑上做基準測試

掌握這項新功能,將讓你的 JavaScript 程式碼更 健全、可維護,也能在日益複雜的前端與後端應用中,保持資料的正確與一致。祝你在開發旅程中玩得開心、寫得順手!