How to Clone Complex JavaScript Objects with structuredClone()
Deep cloning in JavaScript has always been tricky. Developers often relied on hacks like JSON.stringify()
+ JSON.parse()
or external libraries, which only worked for the simplest cases. But now, there’s a native and powerful alternative: structuredClone().
In this guide, you’ll learn how structuredClone()
works, what it supports, where it breaks, how it compares to other methods, and where to use it in real-world apps.
🔄 Why structuredClone()
Is Not Just JSON 2.0
Let’s start with a classic problem.
const data = {
date: new Date(),
regex: /hello/gi,
map: new Map([['key', 'value']]),
};
const jsonCopy = JSON.parse(JSON.stringify(data));
console.log(jsonCopy);
// Output: { date: "2025-02-02T12:00:00.000Z", regex: {}, map: {} }
You lose critical types:
-
Date
becomes a string -
RegExp
becomes an empty object -
Map
becomes
Worse, if your object has circular references, it’ll crash:
const node = {};
node.self = node;
JSON.stringify(node); // ❌ TypeError: Converting circular structure to JSON
Enter structuredClone()
:
const structuredCopy = structuredClone(data);
console.log(structuredCopy.date instanceof Date); // true
console.log(structuredCopy.regex instanceof RegExp); // true
console.log(structuredCopy.map instanceof Map); // true
It handles these cases correctly, deeply, and safely.
✅ What structuredClone()
Supports
This method works with a wide range of types:
✅ Primitives:
undefined
, null
, boolean
, number
, bigint
, string
, symbol
✅ Data Structures:
- Objects and arrays
-
Date
,RegExp
,Map
,Set
- Typed arrays,
ArrayBuffer
,SharedArrayBuffer
-
Blob
,File
,ImageData
,MessagePort
,FileList
- Browser-specific objects like
MessageChannel
,OffscreenCanvas
🔬 Binary Example:
const buffer = new ArrayBuffer(16);
const clone = structuredClone(buffer);
console.log(clone.byteLength); // 16
console.log(clone === buffer); // false
Useful for transferring binary data to Web Workers or BroadcastChannel.
🔁 Deep Nesting and Circular Structures
structuredClone()
can also handle circular references:
const node = { value: 1 };
node.self = node;
const clone = structuredClone(node);
console.log(clone.self === clone); // true
This makes it perfect for serializing graphs, trees, and cyclic structures, where JSON completely fails.
🧱 Limitations of structuredClone()
While powerful, it doesn’t handle everything.
❌ 1. Functions
const withFn = {
sayHi: () => console.log('hi'),
};
structuredClone(withFn); // ❌ DataCloneError
Functions can’t be cloned. Behavior (code) is lost.
❌ 2. DOM Elements
const div = document.createElement('div');
structuredClone(div); // ❌ DataCloneError
UI elements aren’t cloneable — only data is.
❌ 3. Prototypes and Class Instances
class MyClass {
constructor(x) {
this.x = x;
}
}
const instance = new MyClass(42);
const clone = structuredClone(instance);
console.log(clone instanceof MyClass); // false
console.log(clone.x); // 42
Only the plain object structure is cloned — not the prototype chain.
⚖️ Performance: Is It Slow?
Compared to JSON.parse(JSON.stringify(...))
, yes — structuredClone()
is slower for flat structures.
But it’s much more stable, and actually faster for:
- Nested or deeply recursive objects
- Objects with circular references
- Binary data or typed arrays
If performance is critical (e.g., cloning every animation frame), consider alternatives like Object.assign()
(shallow) or lodash/cloneDeep
.
🆚 Alternatives and Polyfills
📦 Lodash cloneDeep
import cloneDeep from 'lodash/cloneDeep';
import cloneDeep from 'lodash/cloneDeep';
const obj = { a: 1, b: { c: 2 } };
const deep = cloneDeep(obj);
Works for nested objects and arrays — but:
- Can’t clone
Map
,Set
,Date
accurately - Won’t handle circular structures unless configured
🌐 Browser Support
- ✅ Supported: Chrome 98+, Firefox 94+, Safari 15+, Node.js 17+
- ❌ Not Supported: IE11, Safari < 14, Node < 17
For older environments, use:
-
structured-clone
polyfill - Fallback to
cloneDeep()
orJSON.parse()
with checks
🚀 Real-World Use Cases
1. Undo/Redo in Rich Editors
When building complex editors (think Notion, Figma, Miro), you need to clone the entire state, including nested structures, maps, and cyclic references.
function saveSnapshot(state) {
historyStack.push(structuredClone(state));
}
- Preserves deep state
- Avoids accidental mutations
- Eliminates bug-prone manual deep clones
2. State Sync Between Tabs with BroadcastChannel
const channel = new BroadcastChannel('sync');
channel.postMessage(structuredClone(store));
On receiving:
channel.onmessage = e => {
syncWithStore(e.data);
};
Ensures:
-
Map
,Date
,Blob
stay intact - Cycles don’t crash the sync
- Fast, native serialization
3. Passing Data to Web Workers
const worker = new Worker('worker.js');
worker.postMessage(structuredClone(bigData));
Structured clone avoids Transferable
limitations and keeps data safe.
⚠️ When Not to Use It
❌ Scenario | Alternative |
---|---|
You need methods or class instances | Manual clone or class-transformer |
Performance-critical, cloning every frame | Shallow copy or tuned lib |
Old browser/Node.js version | Polyfill or fallback |
Cloning UI elements or DOM nodes | Manual serialization |
🧠 Final Thoughts
structuredClone()
is the modern way to deep clone in JavaScript:
- Safe
- Native
- Handles complex structures and cycles
- Ideal for serialization, syncing, undo stacks
It’s especially powerful in React, Vue, and Svelte apps for state snapshotting, and is perfect for passing data to IndexedDB, postMessage
, or between browser tabs.
Use it with care — know where it shines and where it doesn’t. And say goodbye to JSON.parse(JSON.stringify(...))
once and for all.