為什麼推薦用 structureClone 在 JavaScript 做深拷貝?
2024年1月28日
在前端面試的題目中,介紹「淺拷貝與深拷貝」差別,以及「手寫潛拷貝與深拷貝」都是很常會考的題目,有興趣練習的讀者可以參考 《手寫淺拷貝 (shallow copy) 和深拷貝 (deep copy)》。
因為剛好讀到 builder.io 的 Steve 寫的《Deep Cloning Objects in JavaScript, the Modern Way》,想說可以進一步摘要該篇,來更完整講述深拷貝的概念。Steve 這篇的一個重點,在談論為什麼推薦用 structureClone 在 JavaScript 做深拷貝。
深拷貝的定義是,當拷貝時,兩個物件具有相同的屬性名稱和順序,且每個屬性的值也具有相同的結構,且在原型鏈也具有相同的結構。Steve 給的例子如下,可以看到,拷貝完 copied
跟 calendarEvent
的物件屬性都相同,只是 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
是推薦首選。