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

相关推荐
怕浪猫5 分钟前
第一章、Chrome DevTools Protocol (CDP) 详解
前端·javascript·chrome
kyriewen21 分钟前
从本地到生产:迁移到 GitHub Actions 自动化 CI/CD,总结了这 5 个坑
前端·github·自动化运维
雨季mo浅忆34 分钟前
首个Vue3项目边写边学边记
前端·vue3
张忠琳1 小时前
【Go 1.26.4】Golang Select 深度解析
开发语言·后端·golang
IT_陈寒2 小时前
React中useEffect依赖项这个坑我居然踩了三天
前端·人工智能·后端
qq4356947012 小时前
Vue04
前端·vue.js
提笔了无痕2 小时前
如何用Go实现整套RAG流程
开发语言·后端·golang
成都第一深情IZZO3 小时前
事务未提交就发送 MQ,导致消费者读不到订单数据的问题
后端
Yeats_Liao3 小时前
Feed流系统设计(三):数据模型与存储设计,从表结构到Redis收件箱
java·javascript·redis
大橙子打游戏3 小时前
Fable5不能用了,但是依然能让 AI 纯靠截图玩通宝可梦
后端