JavaScript 中深度克隆对象的现代方式

本文介绍了JavaScript中StructuredClone方法,一种现代的深拷贝技术,可以处理复杂数据结构如无限嵌套、循环引用和多种JavaScript类型,包括Date、Set等。与传统方法相比,它提供了更全面的复制功能且性能更好。
摘要由CSDN通过智能技术生成

JavaScript 中深度克隆对象的现代方式

—structuredClone()

在这里插入图片描述

structuredClone

文章出处:https://www.builder.io/blog/structured-clone
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
cocalendarEvent.attendees === copied.attendees // false

没错,structuredClone不仅可以做到以上,还可以:

1、克隆无限嵌套的对象和数组
2、克隆循环引用
3、克隆各种 JavaScript 类型,例如Date、Set、Map、Error、RegExp、ArrayBuffer、Blob、File、ImageData等等
4、转移任何可转移对象

例如,这种变态的数据 甚至会按预期实现:

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

// ✅ All good, fully and deeply copied!
const clonedSink = structuredClone(kitchenSink)

我jio的我翻译不好,直接上原文

Why not just object spread?

It is important to note we are talking about a deep copy. If you just need to do a shallow copy, aka a copy that does not copy nested objects or arrays, then we can just do an object spread:

const simpleEvent = {
  title: "Builder.io Conf",
}
// ✅ no problem, there are no nested objects or arrays
const shallowCopy = {...calendarEvent}

Or even one of these, if you prefer

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

But as soon as we have nested items, we run into trouble:

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

const shallowCopy = {...calendarEvent}

// 🚩 oops - we just added "Bob" to both the copy *and* the original event
shallowCopy.attendees.push("Bob")

// 🚩 oops - we just updated the date for the copy *and* original event
shallowCopy.date.setTime(456)

As you can see, we did not make a full copy of this object.

The nested date and array are still a shared reference between both, which can cause us major issues if we want to edit those thinking we are only updating the copied calendar event object.

Why not JSON.parse(JSON.stringify(x)) ?

Ah yes, this trick. It is actually a great one, and is surprisingly performant, but has some shortcomings that structuredClone addresses.

Take this as an example:

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

// 🚩 JSON.stringify converted the `date` to a string
const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))

If we log problematicCopy, we would get:

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

That’s not what we wanted! date is supposed to be a Date object, not a string.

This happened because JSON.stringify can only handle basic objects, arrays, and primitives. Any other type can be handled in hard to predict ways. For instance, Dates are converted to a string. But a Set is simply converted to {}.

JSON.stringify even completely ignores certain things, like undefined or functions.

For instance, if we copied our **kitchenSink** example with this method:

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

We would get:

{
  "set": {},
  "map": {},
  "regex": {},
  "deep": {
    "array": [
      {}
    ]
  },
  "error": {},
}

Ew!

Oh yeah, and we had to remove the circular reference we originally had for this, as JSON.stringify simply throws errors if it encounters one of those.

So while this method can be great if our requirements fit what it can do, there is a lot that we can do with structuredClone (aka everything above that we failed to do here) that this method cannot.

Why not _.cloneDeep

To date, Lodash’s cloneDeep function has been a very common solution to this problem.

And this does, in fact, work as expected:

import cloneDeep from 'lodash/cloneDeep'

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

const clonedEvent = cloneDeep(calendarEvent)

But, there is just one caveat here. According to the Import Cost extension in my IDE, that prints the kb cost of anything I import, this one function comes in at a whole 17.4kb minified (5.3kb gzipped):

在这里插入图片描述

And that assumes you import just that function. If you instead import the more common way, not realizing that tree shaking doesn’t always work the way you hoped, you could accidentally import up to 25kb just for this one function 😱

在这里插入图片描述

While that will not be the end of the world to anyone, it’s simply not necessary in our case, not when browsers already have structuredClone built in.

What can structuredClone not clone

1、Functions cannot be cloned

They will a throw a DataCloneError exception:

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

2、DOM nodes

Also throws a DataCloneError exception:

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

3、Property descriptors, setters, and getters

As well as similar metadata-like features are not cloned.

For instance, with a getter, the resulting value is cloned, but not the getter function itself (or any other property metadata):

structuredClone({ get foo() { return 'bar' } })
// Becomes: { foo: 'bar' }

4、Object prototypes

The prototype chain is not walked or duplicated. So if you clone an instance of MyClass, the cloned object will no longer be known to be an instance of this class (but all valid properties of this class will be cloned)

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

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

cloned instanceof myClass // false

Full list of supported types

More simply put, anything not in the below list cannot be cloned:

JS Built-ins
Array, ArrayBuffer, Boolean, DataView, Date, Error types (those specifically listed below), Map , Object but only plain objects (e.g. from object literals), Primitive types, except symbol (aka number, string, null, undefined, boolean, BigInt), RegExp, Set, TypedArray

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

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

Browser and runtime support
And here is the best part - structuredClone is supported in all major browsers, and even Node.js and Deno.

Just note the caveat with Web Workers having more limited support:

Browser support table - screenshot from the link directly below this image
Source: MDN

Conclusion
It’s been a long time coming, but we finally now have structuredClone to make deep cloning objects in JavaScript a breeze.

Light bulb tip icon.
Tip: Visit our JavaScript hub to learn more.

About Me

a. student just so so hhh

  • 41
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值