Deep Cloning in JavaScript: The Modern Way. Use structuredClone
Matías Hernández Arellano
Posted on January 26, 2023
A few days ago, I learned that Javascript has a native way of creating deep copies of an object.
This article will explore the native method for deep cloning an object in JavaScript. We will also discuss the difference between shallow and deep copying in JavaScript.
What is a deep copy or clone?
Javascript objects are usually stored in memory and can only be copied by reference, meaning, that a variable does not store an object in itself, but rather an identifier that represents the memory location of the object. Therefore, objects cannot be copied in the same way as primitives.
There are two types of copy within the Javascript world: shallow and deep.
Shallow Copies
A shallow copy is a copy of an object that only copies the reference to the object, not the actual data. If the original object is modified, the copy will also be modified.
For example, let's say we have an object called "originalObject" that looks like this:
let originalObject = {
name: "John",
age: 30,
address: {
street: "123 Main St",
city: "Anytown",
state: "Anystate"
}
}
If we create a shallow copy of this object using the Object.assign()
method, like this:
let shallowCopy = Object.assign({}, originalObject);
The shallowCopy
object will look exactly the same as the originalObject
. However, if we modify the address
property of the originalObject
, the address
property of the shallowCopy
will also be modified because both objects are pointing to the same address object:
originalObject.address.city = "NewCity";
console.log(shallowCopy.address.city); //Output: "NewCity"
Deep copies
On the other hand, A deep copy creates a new object with its own set of data, separate from the original object. If the original object is modified, the copy will not be affected.
For example, let's say we have an object called "originalObject" that looks like this:
let originalObject = {
name: "John",
age: 30,
address: {
street: "123 Main St",
city: "Anytown",
state: "Anystate"
}
}
To create a deep copy of this object, we can use the JSON.parse(JSON.stringify(obj))
method.
let deepCopy = JSON.parse(JSON.stringify(originalObject));
If we now modify the address
property of the originalObject
, the address
property of the deepCopy
will not be modified because they are completely different objects:
originalObject.address.city = "NewCity";
console.log(deepCopy.address.city); //Output: "Anytown"
Please note that JSON.parse(JSON.stringify(obj)) method is not suitable for complex objects with circular references. In this case, you can use a library such as lodash, underscore or a specific deep copy function.
Native deep cloning: structuredClone
As shown in the previous example, Javascript has ways to work around the deep copy problem. In the example, it uses the serialization strategy. Basically, it transforms an object into a JSON representation and then parses it again.
But, now the Web API has a new way of solving the deep cloning issue by using the structuredClone
global method.
This method was recently added to expose the structured clone algorithm, a way to create deep copies of Javascript values that can be used, for example, to transfer JS values from or to a WebWorker.
This algorithm has been part of the HTML spec for a long time but only as a tool used by other APIs. Before structuredClone
was added, you have to do some workarounds to use it, like using postMessage
to send messages to “ourselves”.
Structured Cloning is an algorithm created and used to transfer values from one real into another, like the
postMessage
call that send a message to another window or Webworker.
This is a global method that handles the creation of deep copies of any given value.
const deepCopy = structuredClone(originalObject)
Let’s review a quick example.
const original = {
site: "https://matiashernandez.dev",
published: new Date(),
sociales: [{
name: "twitter",
url: "https://twitter.com/matiasfha"
},{
name: "youtube",
url: "https://dub.sh/channel" //Subscribe!
}]
}
const copy = structuredClone(original)
That is all it takes to create a full/deep copy of the original object.
Limitations
Even tho the structured cloning algorithm address many of the issues with JSON.stringify
it still has some limitations that you need to be aware of.
Can’t clone Function objects: If the object you want to clone contains functions, they will be discarded and a
DataCloneError
will be thrown.Can’t clone DOM nodes
-
Can’t clone some properties like:
-
lastIndex
from aRegexp
object - setters, getters, and similar metadata
- the prototype chain will not be duplicated
-
Alternatives
structuredClone
is relatively, but the need for deep cloning values has been there forever. So let’s check some alternatives to the structuredClone
method that the web development community has been using
Object.assign and spread syntax.
Object.assign
has been the way to create copies for a long time, but here we are talking about deep copy, but Object.assign
can only provide shallow copy, and is not able to copy nested objects or arrays.
The spread syntax was added on ES2018 to the properties of objects to provide a convenient way to perform shallow copies, is the equivalent of
Object.assign
so you can treat them as equals.
const original = {
site: "https://matiashernandez.dev",
published: new Date(),
socials: [{
name: "twitter",
url: "https://twitter.com/matiasfha"
},{
name: "youtube",
url: "https://dub.sh/channel" //Subscribe!
}]
}
const copy1 = Object.assign({}, original)
const copy2 = { ...original }
// Error, this will update the publishedDate property of the original object
copy1.published.setTime(10)
// Error, this will add an empty object to the original object
copy2.socials.push({})
JSON.parse and JSON.stringify
This has been the trick to get a copy that includes nested objects and arrays; it has a really good performance but is still doesn’t entirely solve the problem of the deep copy.
const original = {
site: "https://matiashernandez.dev",
published: new Date(),
socials: [{
name: "twitter",
url: "https://twitter.com/matiasfha"
},{
name: "youtube",
url: "https://dub.sh/channel" //Subscribe!
}]
}
// The publishedDate property is now a string
const copy = JSON.parse(JSON.stringify(original))
The strategy here is first to transform the object into a string using JSON.stringify
this method will serialize each property of the object recursively so, all the nested properties will also be serialized.
Then it uses JSON.parse
to “un-serialize” the serialized object and generate a new object from the source.
The problem with this strategy is the serialization process. Every object in Javascript has a property method named toString
that implements a way to transform the object into a string representation of itself. Is this implementation the one used by JSON.stringify
to serialize each property, the problem comes with properties like the Date
object used in the original
object, it’s serialized to a string, and a string cannot be transformed back to Date
by JSON.parse
.
JSON.stringify
can only handle basic objects, arrays, and primitives. Other types can be handled in unpredictable ways. For example, Dates are converted to strings, while Sets are simply converted to{}
.
lodash.cloneDeep
This has been the “defacto” way of getting a deep copy, but it means you need to add a dependency just to be able to perform a deep copy.
If you import the function, it will cost you 5.3K
gzipped, or if you add the entire library, it will be 25k
gzipped. That’s a lot if you only want to be able to create deep clones.
Browser Support
The best part of using structuredClone
is not only that it has good performance and achieves the task in a good way, but is supported in all major browsers and engines.
Check the canIUse site
Conclusion
If you need to create deep clones of any value in Javascript, reach for the structuredClone
method and “Use the Platform”. It is time to ditch the old habits of using workarounds and embrace a better JS ecosystem.
Posted on January 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.