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...

相关推荐
疏狂难除13 分钟前
spiderdemo第22题与webassembly的跨域
开发语言·javascript·爬虫·rust·wasm·mitmproxy
Python私教16 分钟前
使用 FastAPI 实现文件上传接口:从入门到进阶
后端
谢彦超oooo31 分钟前
HTML5 与前端开发要点
前端·html·html5
Mos_x1 小时前
springboot系列--自动配置原理
java·后端
IT_陈寒1 小时前
Vue 3响应式原理深度拆解:5个90%开发者不知道的Ref与Reactive底层实现差异
前端·人工智能·后端
睡前要喝豆奶粉1 小时前
在.NET Core Web Api中使用JWT并配置UserContext获取用户信息
前端·.netcore
前端加油站1 小时前
一份实用的Vue3技术栈代码评审指南
前端·vue.js
计算机学姐4 小时前
基于SpringBoot的高校社团管理系统【协同过滤推荐算法+数据可视化】
java·vue.js·spring boot·后端·mysql·信息可视化·推荐算法
Jonathan Star8 小时前
沉浸式雨天海岸:用A-Frame打造WebXR互动场景
前端·javascript
工业甲酰苯胺8 小时前
实现 json path 来评估函数式解析器的损耗
java·前端·json