Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 1511

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 62k
Author
Asked: November 25, 20242024-11-25T08:50:09+00:00 2024-11-25T08:50:09+00:00

Deep Cloning Objects in JavaScript, the Modern Way

  • 62k

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:

  const calendarEvent = {   title: "Builder.io Conf",   date: new Date(123),   attendees: ["Steve"] }  // 😍 const copied = structuredClone(calendarEvent)   
Enter fullscreen mode Exit fullscreen mode

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:

  copied.attendees // ["Steve"] copied.date // Date: Wed Dec 31 1969 16:00:00 cocalendarEvent.attendees === copied.attendees // false   
Enter fullscreen mode Exit fullscreen mode

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:

  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)   
Enter fullscreen mode Exit fullscreen mode

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}   
Enter fullscreen mode Exit fullscreen mode

Or even one of these, if you prefer

  const shallowCopy = Object.assign({}, simpleEvent) const shallowCopy = Object.create(simpleEvent)   
Enter fullscreen mode Exit fullscreen mode

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)   
Enter fullscreen mode Exit fullscreen mode

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))   
Enter fullscreen mode Exit fullscreen mode

If we log problematicCopy, we would get:

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

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))   
Enter fullscreen mode Exit fullscreen mode

We would get:

  {   "set": {},   "map": {},   "regex": {},   "deep": {     "array": [       {}     ]   },   "error": {}, }   
Enter fullscreen mode Exit fullscreen mode

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"] }  // ✅ All good! const clonedEvent = structuredClone(calendarEvent)   
Enter fullscreen mode Exit fullscreen mode

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 25kb

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:

  

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

Enter fullscreen mode Exit fullscreen mode



DOM nodes

Also throws a DataCloneError exception:

  

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

Enter fullscreen mode Exit fullscreen mode



Property descriptors, setters, getters

And 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' }

Enter fullscreen mode Exit fullscreen mode



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

Enter fullscreen mode Exit fullscreen mode



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 – cloneDeep is supported in all major browsers, and even Node.js and Deno.

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

Screenshot of the browser support table linked to below

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:

Builder.io demo gif

javascriptwebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 2k
  • Popular
  • Answers
  • Author

    ES6 - A beginners guide - Template Literals

    • 0 Answers
  • Author

    Understanding Higher Order Functions in JavaScript.

    • 0 Answers
  • Author

    Build a custom video chat app with Daily and Vue.js

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.