关于前端:精通-JavaScriptstructuredClone-方法的高级应用

12次阅读

共计 4057 个字符,预计需要花费 11 分钟才能阅读完成。

您是否晓得,当初 JavaScript 中有一种原生的形式能够深拷贝对象?

没错,这个内置于 JavaScript 运行时的 structuredClone 函数就是这样:

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

// 😍
const copied = structuredClone(calendarEvent)

您是否留神到在下面的例子中,咱们不仅复制了对象,还复制了嵌套数组,甚至还包含 Date 对象?

所有正如预期工作:

copied.attendees // ["Steve"]
copied.date // Date: 1969 年 12 月 31 日 16:00:00
cocalendarEvent.attendees === copied.attendees // false

没错,structuredClone不仅能做到上述性能,还可能:

  • 克隆有限嵌套的对象和数组
  • 克隆循环援用
  • 克隆宽泛的 JavaScript 类型,例如DateSetMapErrorRegExpArrayBufferBlobFileImageData,以及许多其余类型
  • 转移任何可转移对象

例如,甚至以下这种疯狂的状况也能如期工作:

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: "Builder.io 大会",}
// ✅ 没问题,没有嵌套对象或数组
const shallowCopy = {...calendarEvent}

甚至如果您更喜爱,能够应用以下形式之一:

const shallowCopy = Object.assign({}, simpleEvent)
const shallowCopy = Object.create(simpleEvent)

然而一旦咱们有了嵌套项,就会遇到麻烦:

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

const shallowCopy = {...calendarEvent}

// 🚩 哎呀 - 咱们刚刚将“Bob”增加到了复制品 * 和 * 原始事件中
shallowCopy.attendees.push("Bob")

// 🚩 哎呀 - 咱们刚刚更新了复制品 * 和 * 原始事件的日期
shallowCopy.date.setTime(456)

如您所见,咱们并没有齐全复制这个对象。

嵌套的日期和数组依然是两者之间的共享援用,如果咱们想要编辑这些,认为咱们只是在更新复制的日历事件对象,这可能会导致咱们遇到重大问题。

为什么不必JSON.parse(JSON.stringify(x))?

啊,是的,这个技巧。实际上这是一个很好的办法,而且出乎意料地高效,但 structuredClone 解决了它的一些毛病。

以此为例:

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

// 🚩 JSON.stringify 将 `date` 转换为了字符串
const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))

如果咱们记录problematicCopy,咱们会失去:

{
  title: "Builder.io 大会",
  date: "1970-01-01T00:00:00.123Z"
  attendees: ["Steve"]
}

这不是咱们想要的!date应该是一个 Date 对象,而不是一个字符串。

这是因为 JSON.stringify 只能解决根本的对象、数组和原始类型。任何其余类型都以难以预测的形式解决。例如,日期被转换为字符串。然而 Set 简略地转换为{}

JSON.stringify甚至齐全疏忽某些货色,如 undefined函数

例如,如果咱们用这种办法复制咱们的 kitchenSink 示例:

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": {},}

呃!

哦,是的,咱们不得不移除咱们最后领有的循环援用,因为 JSON.stringify 如果遇到其中的一个,就会简略地抛出谬误。

因而,尽管如果咱们的需要适宜它能做的事件,这种办法可能很好,但structuredClone(也就是下面咱们未能做到的所有事件)能做许多这种办法不能做的事件。

为什么不必_.cloneDeep?

到目前为止,Lodash 的 cloneDeep 函数始终是这个问题的一个十分常见的解决方案。

实际上,这的确如预期工作:

import cloneDeep from 'lodash/cloneDeep'

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

const clonedEvent = cloneDeep(calendarEvent)

然而,这里只有一个正告。依据我的 IDE 中的 Import Cost 扩大,它打印了我导入的任何货色的 kb 老本,这一个性能的老本为整整 17.4kb 压缩后的大小(5.3kb gzip 压缩后):

而这是假如您仅导入了那个性能。如果您以更常见的形式导入,没有意识到 tree shaking 并不总是依照您心愿的形式工作,您可能意外地仅为这一个性能导入多达 25kb😱

尽管这对任何人来说都不会是世界末日,但在咱们的案例中,这基本不是必要的,不是在浏览器中曾经内置了 structuredClone 的状况下。

什么是 structuredClone 不能 克隆

函数不能被克隆

它们会抛出一个 DataCloneError 异样:

// 🚩 谬误!structuredClone({fn: () => {}})

DOM 节点

也会抛出一个 DataCloneError 异样:

// 🚩 谬误!structuredClone({el: document.body})

属性描述符、设置器和获取器

以及相似的元数据个性都不会被克隆。

例如,对于一个获取器,克隆的是后果值,而不是获取器函数自身(或任何其余属性元数据):

structuredClone({get foo() {return 'bar'} })
// 变成了:{foo: 'bar'}

对象原型

不会遍历或复制原型链。因而,如果您克隆了 MyClass 的一个实例,克隆的对象将不再被辨认为这个类的实例(但这个类的所有无效属性将被克隆):

class MyClass { 
  foo = 'bar' 
  myMethod() { /* ... */}
}
const myClass = new MyClass()

const cloned = structuredClone(myClass)
// 变成了:{foo: 'bar'}

cloned instanceof myClass // false

反对的类型全列表

更简略地说,上面列表中未提到的任何货色都不能被克隆:

JS 内建类型

ArrayArrayBufferBooleanDataViewDateError类型(上面具体列出的那些)、MapObject但仅限于一般对象(例如,来自对象字面量)、原始类型,除了symbol(即numberstringnullundefinedbooleanBigInt)、RegExpSetTypedArray

谬误类型

ErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError

Web/API 类型

AudioDataBlobCryptoKeyDOMExceptionDOMMatrixDOMMatrixReadOnlyDOMPointDomQuadDomRectFileFileListFileSystemDirectoryHandleFileSystemFileHandleFileSystemHandleImageBitmapImageDataRTCCertificateVideoFrame

浏览器和运行时反对

这里是最好的局部 – structuredClone在所有次要浏览器中都失去反对,甚至包含 Node.js 和 Deno。

只需注意 Web Workers 的反对较为无限的正告:

起源:MDN

论断

通过漫长的期待,咱们终于当初有了structuredClone,使得在 JavaScript 中深度克隆对象变得轻而易举。

  • 原文链接:https://www.builder.io/blog/structured-clone
正文完
 0