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.