How to properly clone a JavaScript object

In this article, we'll explore two common approaches to cloning an object: shallow cloning and deep cloning. We'll explain the differences between these methods, the benefits, and limitations of each, and provide examples of how to implement them in your code. Additionally, we'll discuss a modern, native JavaScript solution to deep cloning objects called structuredClone, let’s get into it!

Shallow cloning

When making a shallow copy of an object, you create a duplicate that shares references with the original. In other words, the properties of the copy point to the same underlying values as those of the original object. As a result, changes made to either the copy or the original can affect the other.

It's worth noting that while shallow copies can be useful for certain purposes, they have some limitations. For example, if the original object contains nested objects or collections, changes to these may not be reflected in the shallow copy. In such cases, you may need to consider using a deep copy, which creates a completely independent duplicate of the original object, including all of its nested components.

Creating a shallow copy of an object it’s pretty straightforward in ES6:

With Object.assign

const source = { name 'fer-codes' };

const cloned = Object.assign({}, obj);

With the spread operator:

const source = { name 'fer-codes' };

const cloned = { ...source };

What if we need the source and the copy to be completely independent?

Deep cloning an object

When a deep copy is made, the resulting copy behaves entirely independently from the source object. This means that any changes made to the original object will not affect the deep copy, and vice versa.

Naive approach - Stringify it!

Perhaps you've come across the suggestion that a "quick and easy" solution for creating a deep clone of an object is to use JSON.stringify :

const source = { name: 'fer-codes' }

const cloned = JSON.parse(JSON.stringify(source));

console.log(source === cloned) // false

Using JSON.stringify to create a deep clone of an object can be effective, but there are certain limitations and potential issues to be aware of. In particular, this method may not work as expected when dealing with certain data types, such as functions, date objects, undefined values, Infinity, and regular expressions.

To illustrate these limitations, let's take a look at how the method behaves when these values are present:

const source = {
  boolean: true,
  string: 'string',
  number: 123,
  und: undefined,  // lost
  foo: () => {},  // lost
  date: new Date(),  // stringified
  inf: Infinity,  // forced to 'null'
}

const clone = JSON.parse(JSON.stringify(a));

console.log(typeof source.date);  // Date object
console.log('cloned', clone.date); // stringified version

console.log(typeof source.foo);  // () => { }
console.log('cloned', clone.foo); // undefined

Modern solution - structuredClone

The structuredClone method is a modern, native JavaScript solution for deep cloning objects that simplifies what was once a complex task.

const source = { name: 'fer-codes' }

const cloned = structuredClone(source)

console.log(source === cloned) // false

Although the structuredClone method overcomes many of the limitations associated with JSON.stringify, it still has some limitations of its own. Specifically, if the object you're attempting to clone includes a function or a non-cloneable data type (such as DOM nodes), structuredClone will throw an error.

const source: { method: () => {} }

const cloned = structuredClone(source);
// Error: could not be cloned.

const a = document.createElement('a')
const elements = { anchor: a }

const clonedElements = structuredClone(source);
// Error: could not be cloned.