這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 你是否知道,JavaScript中有一種原生的方法來做對象的深拷貝? 本文我們要介紹的是 structuredClone 函數,它是內置在 JavaScript 運行時中的: const calendarEvent = { title: ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
你是否知道,JavaScript中有一種原生的方法來做對象的深拷貝?
本文我們要介紹的是 structuredClone
函數,它是內置在 JavaScript 運行時中的:
const calendarEvent = { title: "前端修羅場", date: new Date(123), attendees: ["Steve"] } const copied = structuredClone(calendarEvent)
在上面的示例中,我們不僅拷貝了對象,還拷貝了嵌套數組,甚至拷貝了Date
對象:
copied.attendees // ["Steve"] copied.date // Date: Wed Dec 31 1969 16:00:00 cocalendarEvent.attendees === copied.attendees // false
沒錯,structuredClone 不僅可以做到以上這些,而且還可以:
- 克隆無限嵌套的對象和數組
- 克隆迴圈引用
- 克隆各種各樣的 JavaScript 類型,如
Date, Set, Map, Error, RegExp, ArrayBuffer, Blob, File, ImageData
等等 - 轉移任何可轉移的對象
舉個例子:
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)
為什麼不使用對象擴展運算符進行克隆呢
值得註意的是,我們討論的是深拷貝。如果你只需要做一個淺拷貝,也就是一個不複製嵌套
對象或數組的拷貝,那麼我們可以只做一個對象擴展:
const simpleEvent = { title: "前端修羅場", } const shallowCopy = {...calendarEvent}
或者你也可以使用這種方法:
const shallowCopy = Object.assign({}, simpleEvent) const shallowCopy = Object.create(simpleEvent)
但是一旦我們有了嵌套項,我們就會遇到麻煩:
const calendarEvent = { title: "前端修羅場", date: new Date(123), attendees: ["Steve"] } const shallowCopy = {...calendarEvent} shallowCopy.attendees.push("Bob") shallowCopy.date.setTime(456)
如上所見,我們沒有對該對象進行完整複製。
嵌套日期和數組仍然是兩者之間的共用引用,如果我們想編輯它們,認為我們只是更新複製的日曆事件對象,這可能會導致重大問題。
為什麼不使用JSON.parse(JSON.stringify(x)) ?
它實際上是一個很棒的工具,性能令人驚訝,但也有一些structuredClone可以解決的缺點。
舉個例子:
const calendarEvent = { title: "前端修羅場", date: new Date(123), attendees: ["Steve"] } const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))
如果我們輸出 problematicopy,我們會得到:
{ title: "前端修羅場", date: "1970-01-01T00:00:00.123Z" attendees: ["Steve"] }
這不是我們想要的 date 格式,因為格式應該是date對象,而不是字元串。
這是因為 JSON.Stringify
只能處理基本對象、數組和基本類型。任何其他類型都可能以難以預測的方式處理。例如,日期被轉換為字元串。但是 Set 對象就會被簡單地轉換為 {}。
同時,JSON.Stringify
甚至會完全忽略某些東西,如 undefined 或 function。
例如,如果我們用這個方法複製下麵這個例子:
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": {}, }
可以看到, 這種方法的拷貝出錯了。
因此,如果我們的需求適合這個方法,可以用這個方法。但是,我們可以用 structuredClone
做這個方法有很多不能做的事情。
為什麼不是 _.cloneDeep?
到目前為止,Lodash 的 cloneDeep 函數是這個問題的一個非常常見的解決方案。事實上,這確實也像預期的那樣工作:
import cloneDeep from 'lodash/cloneDeep' const calendarEvent = { title: "前端修羅場", date: new Date(123), attendees: ["Steve"] } // ✅ const clonedEvent = structuredClone(calendarEvent)
但是,這裡有一個警告。根據我的 IDE 中的導入成本擴展,列印任何我導入函數的成本,這個函數占了 17.4kb` 的大小(5.3kb gzip):
假設你只導入了這個函數。如果改用更常見的方式導入,沒有意識到搖樹並不總是按希望的方式工作,那麼可能會無意中為這個函數導入高達2 5kb 的文件