Made in Builder

Made in Builder.io

Live Demo 👉 All Demo, No Pitch: Content & Commerce / Builder.io & Elastic Path on 12/13

×

Developers

Product

Use Cases

Pricing

Developers

Resources

Company

Get StartedLogin

Product

Features

Integrations

Talk to an Expert

Pricing

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

‹ Back to blog

Web Development

Deep Cloning Objects in JavaScript, the Modern Way

January 18, 2023

Written By Steve Sewell

Did you know, there's now a native way in JavaScript to do deep copies of objects?

That's right, this structuredClone function is built into the JavaScript runtime:

Did you notice in the example above we not only copied the object, but also the nested array, and even the Date object?

And all works precisely as expected:

That’s right, structuredClone can not only do the above, but additionally:

  • Clone infinitely nested objects and arrays
  • Clone circular references
  • Clone a wide variety of JavaScript types, such as Date, Set, Map, Error, RegExp, ArrayBuffer, Blob, File, ImageData, and many more
  • Transfer any transferable objects

So for example, this madness would even work as expected:

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:

Or even one of these, if you prefer

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

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:

If we log problematicCopy, we would get:

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:

We would get:

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:

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

Screenshot of the import cost of 'lodash/cloneDeep' at 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 😱

Screenshot of the import cost of 'lodash' at 25.2kb gzipped

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

Functions cannot be cloned

They will a throw a DataCloneError exception:

DOM nodes

Also throws a DataCloneError exception:

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

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)

Full list of supported types

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

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

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. Thank you, Surma.

About Me

Hi! I'm Steve, CEO of Builder.io.

We make a way to drag + drop with your components to create pages and other CMS content on your site or app, visually.

You can read more about how this can improve your workflow here.

You may find it interesting or useful:

Visually build with your components

Builder.io is a Visual CMS that let's you drag and drop to create content on your site visually, using your components.

Stop drowning in a backlog of requests - build with your whole team instead.

Try it out
Learn more

Share

Twitter
LinkedIn
Facebook

Continue Reading
Web Performance14 MIN
Optimal Images in HTML
WRITTEN BYSteve Sewell
January 26, 2023
web development9 MIN
The Tailwind CSS Drama Your Users Don't Care About
WRITTEN BYYoav Ganbar
January 25, 2023
Web Development9 MIN
Fast and Light Relative Time Strings in JS
WRITTEN BYSteve Sewell
January 24, 2023

Product

Visual CMS

Theme Studio for Shopify

Sign up

Login

Featured Integrations

React

Angular

Next.js

Gatsby

Get In Touch

Chat With Us

Twitter

Linkedin

Careers

© 2020 Builder.io, Inc.

Security

Privacy Policy

Terms of Service

Visually build and optimize digital experiences on any tech stack. No coding required, and developer approved.

Sign up

Log in

DEVELOPERS

Builder for Developers

Developer Docs

Github

JSX Lite

Qwik

INTEGRATIONS

React

Angular

Next.js

Gatsby

PRODUCT

Product features

Pricing

RESOURCES

User Guides

Blog

Forum

Templates

COMPANY

About

Careers 🚀

Visually build and optimize digital experiences on any tech stack. No coding required, and developer approved.
Get Started
Log in

DEVELOPERS

Builder for Developers

Developer Docs

Open Source Projects

Performance Insights

PRODUCT

Features

Pricing

RESOURCES

User Guides

Blog

Community Forum

Templates

Partners

Submit an Idea

INTEGRATIONS

React

Next.js

Gatsby

Angular

Vue

Nuxt

Hydrogen

Salesforce

Shopify

All Integrations

Security

Privacy Policy

Terms of Service

By clicking “Subscribe”, I agree to Builder.io's Terms of Service and Privacy Policy.