How does JavaScript's structuredClone() differ from other cloning methods?
Cloning objects in JavaScript is a minefield of edge cases. Shallow copies break with nested data. JSON.stringify() drops functions and special types. Even custom deep clone functions may occasionally miss some details.
Deep clone object
Learn how JavaScript handles mutable data, such as objects and arrays, and understand how shallow cloning and deep cloning work.
Luckily, structuredClone(), a rather new addition to JavaScript, provides a robust solution for deep cloning objects. Yet, the structured clone algorithm has its own quirks and limitations. In this article, we'll explore how it works, how it compares to other cloning methods, and when to use it.
Cloning methods overview
Let's start by comparing the available cloning methods. For simplicity, we'll look at four common approaches, as shown below:
const shallowClone = obj => ({ ...obj });
const deepClone = obj => {
11 collapsed lines
if (obj === null) return null;
let clone = Object.assign({}, obj);
Object.keys(clone).forEach(
key =>
(clone[key] =
typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
);
if (Array.isArray(obj)) {
clone.length = obj.length;
return Array.from(clone);
}
return clone;
};
const jsonClone = obj => JSON.parse(JSON.stringify(obj));
shallowClonecreates a shallow copy of the object, meaning nested objects are still references to the original.deepClonerecursively clones the object, handling nested objects and arrays.jsonCloneusesJSON.stringify()andJSON.parse()to create a deep clone, but it has limitations with various data types.structuredClone()is the new built-in method that uses the structured clone algorithm, which handles many edge cases that the other methods do not.
For a detailed explanation of cloning methods, see the previous article on the topic.
Comparison table
| Feature | shallowClone | deepClone | JSON | structuredClone |
|---|---|---|---|---|
| Nested objects/arrays | โ ๏ธ | โ | โ | โ |
| Functions | โ ๏ธ | โ ๏ธ | โ | โ Error |
| Built-in types | โ ๏ธ | โ[1] | โ | โ |
| DOM nodes | โ ๏ธ | โ[2] | โ | โ Error |
| Circular references | โ ๏ธ | โ[3] | โ Error | โ |
| Prototype chain | โ | โ[4] | โ | โ |
| Getters/Setters | โ | โ[5] | โ | โ |
| Symbol properties | โ | โ | โ | โ |
| Private fields | โ | โ | โ | โ |
[1] Built-in types are often not cloned correctly, unless specifically handled
[2] Cloning DOM nodes is inconsistent and can vary by implementation
[3] Circular references will throw an error, unless specifically handled
[4] The prototype chain could be preserved, depending on the implementation
[5] Property descriptors can be preserved, if handled explicitly
Special cases
Given the previous table, let's explore each of the special cases in more detail, focusing on how structuredClone() differs from the other methods. We'll skip the nested object and arrays, as they are handled similarly by all methods except shallow cloning.
Functions
Neither structuredClone() nor JSON.stringify() clone functions. Shallow and custom deep clones copy function references, without cloning the function itself. This means that the cloned object will still reference the original function, which can lead to unexpected behavior, especially combined with closures or this context.
Closures introduction
Learn and understand closures, a core concept in JavaScript programming, and level up your code.
const obj = { fn: () => 1 };
shallowClone(obj).fn === obj.fn; // true
deepClone(obj).fn === obj.fn; // true
jsonClone(obj).fn; // null
structuredClone(obj); // throws DataCloneError
Built-in types
Built-in objects, like Date, Map, Set, and RegExp, are not cloned correctly by JSON.stringify(). They are either converted to strings or empty objects. Shallow clones copy references, while custom deep clones may fail unless specifically handled. But, structuredClone() handles these types correctly, preserving their structure and properties when cloning them.
The structured clone algorithm handles Error types a little differently. You may want to check out the official MDN documentation.
const obj = {
date: new Date('2025-06-07'),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
regexp: /abc/g,
};
shallowClone(obj).date === obj.date; // true (Date object, same for others)
deepClone(obj).date; // Empty object (same for others)
jsonClone(obj).date; // String representation (the rest are empty objects)
structuredClone(obj).date === obj.date; // true (Date object, same for others)
Almost all native types have straightforward constructor-based solutions you can use for cloning them, if you can't use structuredClone(). You can add special handling for them in your custom deep clone function, if needed.
DOM nodes
DOM nodes cannot be cloned with structuredClone(). Attempting to do so throws a DataCloneError. This makes sense when you consider what it means to clone a DOM node: it would require creating a new node in the document. Other options don't handle this gracefully either, but they don't throw errors, which might actually be worse.
const obj = { node: document.querySelector('body') };
shallowClone(obj).node === obj.node; // true
deepClone(obj).node; // Empty DOM node
jsonClone(obj).node; // Empty object
structuredClone(obj); // throws DataCloneError
Circular references
structuredClone() handles circular references out of the box. JSON.stringify() throws and error, and most custom deep clones fail unless specifically designed for handling circular references.
const obj = {};
obj.self = obj;
shallowClone(obj).self === obj; // true
deepClone(obj).self; // throws RangeError: Maximum call stack size exceeded
JSON.stringify(obj); // throws TypeError: Converting circular structure to JSON
structuredClone(obj).self === obj; // false
Convert circular JSON to string
Circular JSON objects can't be serialized using JSON.stringify(), but you can use this trick to handle them.
Prototype chain
The most surprising letdown of structuredClone() is that it does not preserve the prototype chain. The cloned object always has Object.prototype as its prototype, regardless of the original object's prototype. Yet, none of the other methods preserve the prototype chain either, except if handled explicitly in a custom deep clone function.
const proto = { x: 1 };
const obj = Object.create(proto);
shallowClone(obj).x; // undefined
deepClone(obj).x; // undefined
jsonClone(obj).x; // undefined
structuredClone(obj).x; // undefined
Prototypal vs classical inheritance
Understanding the difference between these two object-oriented programming paradigms is key to taking your skills to the next level.
Getters and setters
Another shortcoming shared among all options, including structuredClone(), is that getters and setters are not preserved. Instead, only the current value is cloned, which means that the cloned object does not have the same accessors as the original. In general, no property descriptors or similar metadata-like features are preserved by any of the methods, unless explicitly handled in a custom deep clone function.
const obj = {
valueField: 42,
get value() {
return this.valueField;
},
set value(val) {
this.valueField = val;
},
};
typeof obj.value; // object (getter/setter)
typeof shallowClone(obj).value; // number (independent values)
typeof deepClone(obj).value; // number (independent values)
typeof jsonClone(obj).value; // number (independent values)
typeof structuredClone(obj).value; // number (independent values)
Add key-value pair to object
Adding a key-value pair to a JavaScript object is straightforward, yet there are multiple ways available to do so.
Symbol properties
structuredClone() does not clone properties keyed by Symbols. Instead, it ignores them, just like JSON.stringify(). Shallow and custom deep clones copy Symbol properties as references.
const sym = Symbol('key');
const obj = { [sym]: 'value' };
shallowClone(obj)[sym] === obj[sym]; // true
deepClone(obj)[sym] === obj[sym]; // true
jsonClone(obj)[sym]; // undefined
structuredClone(obj)[sym]; // undefined
Private fields
Private class fields and methods are also not cloned by structuredClone(). Only public, enumerable properties are included in the clone. This is consistent with how other methods handle private properties, as they are not part of the object's enumerable properties.
class Secret {
#hidden = 42;
value = 7;
get hidden() {
return this.#hidden;
}
};
const obj = new Secret();
shallowClone(obj); // #hidden is not cloned
deepClone(obj); // #hidden is not cloned
jsonClone(obj); // #hidden is not cloned
structuredClone(obj); // #hidden is not cloned
Performance considerations
Given the fact that structuredClone() is a built-in method, it is generally optimized for performance. It is faster than custom deep clone functions and JSON.stringify(), especially for complex objects with circular references or built-in types.
As a rule of thumb, if structuredClone() can handle your use case, its performance is likely better than any custom solution you could write. Add on top the fact that it may improve over time as JavaScript engines optimize it, and you have a solid choice for deep cloning.
When to use structuredClone()
As mentioned already, structuredClone() is a powerful tool for deep cloning objects in JavaScript, natively, without hassle and headaches. That being said, if you don't explicitly need to handle some special edge case that a custom deep clone function would handle, you should prefer using structuredClone() over other methods. It is fast, safe, and handles many edge cases that break other methods.
You may consider favoring a custom deep clone function if you need to:
- Preserve the prototype chain of the original object.
- Clone functions or DOM nodes.
- Preserve property descriptors, getters, or setters.
- Preserve symbol properties.
Even in such scenarios, you can often write a thin wrapper around structuredClone() to handle any cases you need, while still benefiting from its performance and reliability.
The structured clone algorithm may throw a DataCloneError in scenarios not covered here, especially platform-specific objects or types. Always test your use case to ensure it works as expected. For a full list of supported types, see MDN's page on the Structured clone algorithm.
Conclusion
In conclusion, structuredClone() is a powerful and versatile method for deep cloning objects in JavaScript. It handles many edge cases that other methods struggle with, such as circular references, built-in types, and complex data structures. While it has some limitations, such as not preserving the prototype chain or cloning functions, it is still a great choice for most everyday use cases.