美文网首页
JavaScript中深度克隆对象的新方法

JavaScript中深度克隆对象的新方法

作者: 涅槃快乐是金 | 来源:发表于2024-04-08 19:59 被阅读0次

你知道吗,在JavaScript中现在有一种原生方法可以深度复制对象吗?

没错,这个structuredClone函数已经内置到JavaScript运行时中了:

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

// 😍
const copied = structuredClone(calendarEvent)

上面的示例中,我们不仅复制了对象,还复制了嵌套数组,甚至是Date对象吗?

而且一切都正如预期的那样运作:

copied.attendees // ["Steve"]
copied.date // Date: Wed Dec 31 1969 16:00:00
calendarEvent.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 Conf",
}
// ✅ 没问题,没有嵌套对象或数组
const shallowCopy = {...calendarEvent}

或者是这些,如果你更喜欢的话:

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

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

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

const shallowCopy = {...calendarEvent}

// 🚩 哎呀 - 我们刚刚把"Bob"添加到了复制品和原始事件中
shallowCopy.attendees.push("Bob")

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

我们并没有完全复制这个对象。

嵌套的日期和数组仍然是两者之间共享的引用,这可能会在我们想要编辑它们时造成重大问题,我们可能会认为只更新了复制的日历事件对象。

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

啊,是的,这个技巧。实际上是一个很好的方法,而且出乎意料地高效,但它有一些缺点。

举个例子:

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

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

如果我们记录 problematicCopy,我们会得到:

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

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

这是因为JSON.stringify只能处理基本对象、数组和原始值。任何其他类型可能以难以预测的方式处理。例如,日期被转换为字符串。但是集合只是转换为 {}

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?

迄今为止,LodashcloneDeep函数一直是解决这个问题的一个非常常见的解决方案。

实际上,这确实像预期的那样工作:

import cloneDeep from 'lodash/cloneDeep'

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

const clonedEvent = cloneDeep(calendarEvent)

但是,这里只有一个注意事项。根据我的IDE中的Import Cost扩展,,这个函数最小化后的大小为17.4kb(gzip后为5.3kb)


虽然这假设您只导入了该函数。您可能会为这个函数增加多达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

structuredClone支持的类型列表:

简单地说,任何不在以下列表中的内容都无法被克隆:

JS 内置类型

ArrayArrayBufferBooleanDataViewDateError(特别列出的那些)、MapObject(但只限于普通对象,例如对象字面量)、原始类型(除了 symbol,即数字、字符串、nullundefined、布尔值、BigInt)、RegExpSetTypedArray

Error 类型

ErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError

Web/API 类型

AudioDataBlobCryptoKeyDOMExceptionDOMMatrixDOMMatrixReadOnlyDOMPointDomQuadDomRectFileFileListFileSystemDirectoryHandleFileSystemFileHandleFileSystemHandleImageBitmapImageDataRTCCertificateVideoFrame

浏览器支持:

结构化克隆在所有主流浏览器中都得到支持,甚至在Node.jsDeno中也是如此。只需注意 Web Workers的支持有一些限制。


我们现在有了 structuredClone,让在JavaScript中深度克隆对象变得轻而易举

相关文章

  • JS实现深度克隆

    一、概念 深度克隆:深度克隆的新对象可以完全脱离原对象,我们对新对象的修改不会反映到原对象中 二、知识点储备: 1...

  • js对象的克隆

    在工作中对对象的克隆在所难免,现在就总结一下克隆对象的方法。 浅克隆 深度克隆 其实除了以上的方法,还有一种非常简...

  • JavaScript——深度克隆

    为什么要使用深度克隆? 直接克隆的话,克隆类中的数组只是获得了原始类中的数组指向。这样的话,原始类和克隆类中的...

  • Javascript 基于原型的面向对象系统编程

    Javascript是使用克隆的原型模式。 1. 原型编程的规则 所有的数据都是对象(javascript中不是所...

  • 深度克隆对象

    克隆的概念 浅度克隆:原始类型为值传递,对象类型仍为引用传递。 深度克隆:所有元素或属性均完全复制,与原对象完全脱...

  • 对象深度克隆

    是否遇到过这样的事情 let obj = {name: 'test',age: 1}_obj.age = 2con...

  • 再探原型模式

    再探原型模式 一切都是对象 在JavaScript这门语言中,获取对象的唯一途径就是克隆,而JavaScript中...

  • 对象的深度克隆

    1、数据类型:* 数据分为基本的数据类型(String, Number, boolean, Null, Undef...

  • 对象的深度克隆

    首先了解js的数据类型。可以分为原始数据类型和对象类型数据。 原始数据类型指的是number、string、boo...

  • js clone obj

    深度克隆JavaScript对象是困难的,并且也没有什么简单的解决方案。你可以使用原生的解决方案:JSON.par...

网友评论

      本文标题:JavaScript中深度克隆对象的新方法

      本文链接:https://www.haomeiwen.com/subject/btuxxjtx.html