[譯]在 JavaScript 使用 structuredClone 深度複製
很長一段時間在 JavaScript 對於深度複製或說深拷貝我們都需要自己處理或者使用函式庫。現在平台支援了 structuredClone
雖然在撰文的現階段各大瀏覽器的這個功能都還在試驗階段,但您可以持續觀察Caniuse。目前 Firefox 94 已經在正式版搭載,此外還有 Node 17,Deno 1.14 都支援了這個 API ,可以直接使用。
淺層複製
在 JavaScript 中複製值幾乎都屬於淺層複製/淺拷貝,和深度複製相反,意思是當您變更一個巢狀物件副本的值時(複製的物件屬性還包含著物件)會影響原本的物件屬性,反之亦然。因為複製的是參考。舉個例子我們常使用 展開語法 ...
實作
1 | const original = { |
對複製物件的第一層一般屬性變更值不會有什麼問題
1 | shallowCopy.prop = 'a new prop'; |
但如果變更的屬性是物件的(巢狀物件)下一層屬性時就會影響
1 | shallowCopy.b.num = 2; |
展開語法會遍歷物件屬性和值並將它們加入新建立的物件裡。因此達成了物件的複製,但關於值的部分卻有不同類型。原始值 (不屬於物件的資料類型 string
, number
, bigint
, boolean
, undefined
, symbol
, null
) 沒有問題。
但非原時值的資料如物件它們複製的時候是複製參考。也就造成上面說的會互相影響的問題。
深度複製
和淺層複製相反,深度複製會使用遞迴的方式處理,每一層的物件都會往下複製,直到下層沒有參考為止。這對於讓本尊和副本之間不要互相影響非常重要。在過去其實沒什麼很優的方式處理。大部分的人可能使用第三方函式庫例如 Lodash 的 cloneDeep()
或者
1 | const copy = JSON.parse(JSON.stringify(original)); |
事實上上面這個方法在過去甚至非常流行,因為效能好。但其實這招也是有缺點
- 遞迴資料結構﹔
JSON.stringify()
無法處理遞迴資料結構(裡面包含了自己的參考) - 內建型別﹔
JSON.stringify()
無法處理 JS 一些內建型別例如Map
,Set
,Date
,RegExp
,ArrayBuffer
- 函式﹔
JSON.stringify()
無法處理函式
結構複製
實際上,平台本身就有很多地方需要深度複製例如﹔將資料儲存在 IndexedDB 時序列化和反序列化。利用 postMessage()
將資料傳給 Web Worker 等情境都需要類似的處理。而內部其實是使用一種稱為結構複製的演算法,過去這功能並沒有提供給開發者。
但現在我們可以使用 structuredClone()
了。
1 | const copy = structuredClone(original); |
功能與限制
結構複製解決了大部分 JSON.stringify()
的問題,可以使用遞迴資料結構,JS 內建型別,效能也不錯。
但還是有些限制
- Prototypes﹔如果您使用
structuredClone()
複製某類別物件實例 Class Instance 您只會取得單純的物件不會包涵prototype
的部分 - Function﹔如果您的物件包涵了函式則會被移除
- 不可複製﹔有些值是不可複製的例如
Error
和 DOM 節點
如果您的需求踩到上述的限制,建議您可以使用繼續 Lodash 來處理。
效能
目前尚未對其執行完整的性能測試,但之前 2018 年的時候曾經做過,當時 JSON.parse()
對於小型物件是快一點,預期結果應該是差不多的。但還是推薦使用 structuredClone()
結論
如果您需要深度複製資料您可以考慮嘗試使用 structedClone()
[譯]在 JavaScript 使用 structuredClone 深度複製
https://andyyou.github.io/2021/12/19/javascript-structured-clone-2021/