JavaScript中现代深度克隆方法

知道吗,在JavaScript中现在有一种原生的方法来做对象的深层复制?

没错,这个 structuredClone 函数内置在JavaScript运行时中:

js 复制代码
const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"]
}

const copied = structuredClone(calendarEvent)

所有的结果都如预期的那样:

js 复制代码
copied.attendees // ["Steve"]
copied.date // Date: Wed Dec 31 1969 16:00:00
cocalendarEvent.attendees === copied.attendees // false

是的, structuredClone 不仅可以做以上的事情,而且还可以:

  • 克隆无限嵌套的对象和数组
  • 克隆循环引用
  • 克隆各种各样的JavaScript类型,如 DateSetMapErrorRegExpArrayBufferBlobFileImageData
  • 转让任何可转移对象

例如:

js 复制代码
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)

值得注意的是,我们谈论的是一个深度副本。如果你只需要做一个浅拷贝,也就是不复制嵌套对象或数组的拷贝,那么我们可以只做一个对象扩展:

js 复制代码
const simpleEvent = {
  title: "Builder.io Conf",
}

const shallowCopy = {...calendarEvent}

如果你喜欢的话,也可以是这个

js 复制代码
const shallowCopy = Object.assign({}, simpleEvent)
const shallowCopy = Object.create(simpleEvent)

但是一旦我们有了嵌套项,我们就会遇到麻烦:

js 复制代码
const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"]
}

const shallowCopy = {...calendarEvent}

shallowCopy.attendees.push("Bob")

shallowCopy.date.setTime(456)

正如你所看到的,我们没有做这个对象的完整副本。

为什么不是 JSON.parse(JSON.stringify(x))

举个例子:

js 复制代码
const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"]
}

const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))

如果我们打印 problematicCopy ,我们将得到:

js 复制代码
{
  title: "Builder.io Conf",
  date: "1970-01-01T00:00:00.123Z"
  attendees: ["Steve"]
}

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

这是因为 JSON.stringify 只能处理基本的对象、数组和原语。任何其他类型都难以预测。例如,日期转换为字符串。Set 只是转换为 {}

JSON.stringify 甚至完全忽略了某些东西,比如 undefined 或函数。

例如,如果我们复制 kitchenSink

js 复制代码
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))

我们将得到:

js 复制代码
{
  "set": {},
  "map": {},
  "regex": {},
  "deep": {
    "array": [
      {}
    ]
  },
  "error": {},
}

_.cloneDeep

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

事实上,这是按照预期工作的:

js 复制代码
import cloneDeep from 'lodash/cloneDeep'

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

const clonedEvent = cloneDeep(calendarEvent)

structuredClone 什么不能克隆

不能克隆函数

会抛出一个 DataCloneError 异常:

js 复制代码
// 🚩 Error!
structuredClone({ fn: () => { } })

DOM节点

也会抛出 DataCloneError 异常:

js 复制代码
// 🚩 Error!
structuredClone({ el: document.body })

属性描述符、setter和getter

以及类似元数据的功能不会被克隆。

例如,使用getter,结果值被克隆,但getter函数本身(或任何其他属性元数据)不会被克隆:

js 复制代码
structuredClone({ get foo() { return 'bar' } })
// Becomes: { foo: 'bar' }

对象原型

原型链不会被遍历或复制。因此,如果您克隆 MyClass 的实例,则克隆的对象将不再是该类的实例(但该类的所有有效属性将被克隆)。

js 复制代码
class MyClass { 
  foo = 'bar' 
  myMethod() { /* ... */ }
}
const myClass = new MyClass()

const cloned = structuredClone(myClass)
// Becomes: { foo: 'bar' }

cloned instanceof myClass // false

支持类型的完整列表

更简单地说,任何不在下面列表中的内容都不能被克隆:

js 内置

ArrayArrayBufferBooleanDataViewDateError 类型(下面特别列出的)、 MapObject ,但仅限于普通对象(例如,从对象字面量),原始类型,除了 symbol (又名 numberstringnullundefinedbooleanBigInt ), RegExpSetTypedArray

错误类型

Error, EvalError, RangeError, ReferenceError , SyntaxError, TypeError, URIError

WEB/API 类型

AudioData, Blob, CryptoKey, DOMException, DOMMatrix, DOMMatrixReadOnly, DOMPoint, DomQuad, DomRect, File, FileList, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemHandle, ImageBitmap, ImageData, RTCCertificate, VideoFrame

原文: www.builder.io/blog/struct...

相关推荐
J***Q2923 小时前
Vue数据可视化
前端·vue.js·信息可视化
S***26753 小时前
基于SpringBoot和Leaflet的行政区划地图掩膜效果实战
java·spring boot·后端
ttod_qzstudio5 小时前
深入理解 Vue 3 的 h 函数:构建动态 UI 的利器
前端·vue.js
芳草萋萋鹦鹉洲哦5 小时前
【elemen/js】阻塞UI线程导致的开关卡顿如何优化
开发语言·javascript·ui
_大龄5 小时前
前端解析excel
前端·excel
1***s6325 小时前
Vue图像处理开发
javascript·vue.js·ecmascript
槁***耿5 小时前
JavaScript在Node.js中的事件发射器
开发语言·javascript·node.js
一叶茶5 小时前
移动端平板打开的三种模式。
前端·javascript
前端大卫5 小时前
一文搞懂 Webpack 分包:async、initial 与 all 的区别【附源码】
前端
U***49835 小时前
JavaScript在Node.js中的Strapi
开发语言·javascript·node.js