為什麼推薦用 structureClone 在 JavaScript 做深拷貝?

2024年1月28日

💎 加入 E+ 成長計畫 如果你喜歡我們的內容,歡迎加入 E+,獲得更多深入的軟體前後端內容

在前端面試的題目中,介紹「淺拷貝與深拷貝」差別,以及「手寫潛拷貝與深拷貝」都是很常會考的題目,有興趣練習的讀者可以參考 《手寫淺拷貝 (shallow copy) 和深拷貝 (deep copy)》

因為剛好讀到 builder.io 的 Steve 寫的《Deep Cloning Objects in JavaScript, the Modern Way》,想說可以進一步摘要該篇,來更完整講述深拷貝的概念。Steve 這篇的一個重點,在談論為什麼推薦用 structureClone 在 JavaScript 做深拷貝。

深拷貝的定義是,當拷貝時,兩個物件具有相同的屬性名稱和順序,且每個屬性的值也具有相同的結構,且在原型鏈也具有相同的結構。Steve 給的例子如下,可以看到,拷貝完 copiedcalendarEvent 的物件屬性都相同,只是 cocalendarEvent.attendees === copied.attendees 會是 false

const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"],
};

// 😍
const copied = structuredClone(calendarEvent);

//
copied.attendees; // ["Steve"]
copied.date; // Date: Wed Dec 31 1969 16:00:00
cocalendarEvent.attendees === copied.attendees; // false

《手寫淺拷貝 (shallow copy) 和深拷貝 (deep copy)》 有提到,要深拷貝,可以有幾個方法,一個是用 JSON.parse(JSON.stringify(obj)) 另一個是用 structuredClone(obj)。而在面試中,比較常會問到如何自己手寫 Lodash 的 _.cloneDeep(obj)

在 Steve 的文章中,他推薦使用原生的 structuredClone 函式,基本上如 Steve 所說,現在如果寫 JavaScript 要做深拷貝,structuredClone 都是最推薦的。你可能會問為什麼呢?

首先如 《手寫淺拷貝 (shallow copy) 和深拷貝 (deep copy)》 提到的,JSON.parse(JSON.stringify(obj)) 的問題在於,沒辦法處理 JavaScript 原生類別、純陣列與純物件以外的東西,所以假如今天物件當中有放 Set,則會被轉換成 {},這顯然不理想。

而 Lodash 的 _.cloneDeep(obj) 雖然也能有效處理各種類別的東西,只是一個問題是,Lodash 一貫以來被人詬病導入的成本太高了,如果用

import cloneDeep from "lodash/cloneDeep"

會佔用 17.4kb (或者 5.3kb gzipped)。

如果是用

import { cloneDeep } from "lodash"

直接從 lodash 引入則會是 71.5kb (或 25.2kb gzipped)。

從效能的角度來講,如果可以用更少成本的方式,做到一樣的事情,當然會選擇用成本少的。即使現在有 lodash-es 佔用更小的包體積空間,但額外引入還是多的。如果用原生的 structuredClone 就能免去這問題。

當然,structuredClone 遇到某些東西還是沒辦法直接拷貝,例如函式作為鍵的時候,或者 DOM 節點作為值的時候,都會遇到 DataCloneError。在 Steve 的原文中有提到更細的點,有興趣的讀者可以再自行閱讀。

目前 structuredClone 在各大執行環境都已經被支援,包含 Chrome、Edge、Safari、Node.js、Deno。不過在 workers 的支援度上,有一些瀏覽器與 Node.js 還沒支援。除非你要用到 workers,不然現在要做深拷貝,structuredClone 是推薦首選。

🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們