JavaScript之structuredClone現代深複製

發表於2024-03-04

在JavaScript中,實現深複製的方式有很多種,每種方式都有其優點和缺點。今天介紹一種原生JavaScript提供的structuredClone實現深複製。

下面列舉一些常見的方式,以及它們的程式碼示例和優缺點:

1. 使用JSON.parse(JSON.stringify(obj))

程式碼示例:

function deepClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}

優點:簡單易行,對於大多數物件型別有效。

缺點:不能複製原型鏈,對於包含迴圈引用的物件可能出現問題。比如以下程式碼:

const calendarEvent = {
  date: new Date()
}

const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))

最終得到的date不是Data物件,而是字串。

{
    "date": "2024-03-02T03:43:35.890Z"
}

這是因為JSON.stringify只能處理基本的物件、陣列。任何其他型別都沒有按預期處理。例如,日期轉換為字串。Set/Map只是轉換為{}

const kitchenSink = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [ new File(someBlobData, 'file.txt') ] },
  error: new Error('Hello!')
}

const veryProblematicCopy = JSON.parse(JSON.stringify(kitchenSink))

最終得到如下資料:

{
  "set": {},
  "map": {},
  "regex": {},
  "deep": {
    "array": [
      {}
    ]
  },
  "error": {},
}

2. 使用遞迴

程式碼示例:

function deepClone(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    let clone = obj.constructor();
    for (let attr in obj) {
        if (obj.hasOwnProperty(attr)) {
            clone[attr] = this.deepClone(obj[attr]);
        }
    }
    return clone;
}

優點:對於任何型別的物件都有效,包括迴圈引用。

缺點:對於大型物件可能會消耗大量記憶體,並可能導致堆疊溢位。

3. 第三方庫,如 lodash 的 _.cloneDeep 方法

程式碼示例:

const _ = require('lodash');
function deepClone(obj) {
    return _.cloneDeep(obj);
}

優點:支援更多型別的物件和庫,例如,支援 Proxy 物件。

缺點:會引入依賴導致專案體積增大。

因為這個函式會導致17.4kb的依賴引入,如果只是引入lodash會更高。

4. 現代深複製structuredClone

在現代瀏覽器中,可以使用 structuredClone 方法來實現深複製,它是一種更高效、更安全的深複製方式。

以下是一個示例程式碼,演示如何使用 structuredClone 進行深複製:

const kitchenSink = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [ new File(someBlobData, 'file.txt') ] },
  error: new Error('Hello!')
}
kitchenSink.circular = kitchenSink

const clonedSink = structuredClone(kitchenSink)

structuredClone可以做到:

  • 複製無限巢狀的物件和陣列
  • 複製迴圈引用
  • 複製各種各樣的JavaScript型別,如DateSetMapErrorRegExpArrayBufferBlobFileImageData

哪些不能複製:

  • 函式
  • DOM節點
  • 屬性描述、settergetter
  • 物件原型鏈

所支援的完整列表:

ArrayArrayBufferBooleanDataViewDateError型別(下面具體列出的型別)、MapObject,但僅限於普通物件、原始型別,除了symbol(又名numberstringnullundefinedbooleanBigInt)、RegExpSetTypedArray

Error型別:

Error, EvalError, RangeError, ReferenceError , SyntaxError, TypeError, URIError

Web/API型別:

AudioData, Blob, CryptoKey, DOMException, DOMMatrix, DOMMatrixReadOnly, DOMPoint, DomQuad, DomRect, File, FileList, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemHandle, ImageBitmap, ImageData, RTCCertificate, VideoFrame

值得慶幸的是 structuredClone 在所有主流瀏覽器中都受支援,也支援Node.js和Deno。

最後

我們現在終於可以直接使用原生JavaScript中的structuredClone能力實現深度複製物件。每種方式都有其優缺點,具體使用方式取決於你的需求和目標物件的型別。

參考

  • Deep Cloning Objects in JavaScript, the Modern Way(www.builder.io/blog/structured-clone)
  • mozilla structuredClone(developer.mozilla.org/zh-CN/docs/Web/API/structuredClone)

看完本文如果覺得有用,記得點個贊支援,收藏起來說不定哪天就用上啦~

專注前端開發,分享前端相關技術乾貨,公眾號:南城大前端(ID: nanchengfe)

相關文章