Structured Clone
Deep-clone supported types with structuredClone — what transfers, what throws, vs JSON tricks and MessageChannel history.
- javascript
- structured-clone
You need a deep copy that handles Date, Map, Set, circular refs, and binary data. JSON.parse(JSON.stringify(x)) drops types and dies on cycles. structuredClone is the platform algorithm browsers already used for postMessage — now a global function.
const original = {
d: new Date(),
m: new Map([['a', 1]]),
s: new Set([1, 2]),
n: 1n,
buf: new Uint8Array([1, 2, 3]),
};
original.self = original; // cycle
const copy = structuredClone(original);
copy.m.get('a'); // 1
copy.self === copy; // true
copy.self !== original; // true
copy.d instanceof Date; // true
What clones / what fails
| Supported (common) | Throws DataCloneError |
|---|---|
| plain objects/arrays | functions |
Date, RegExp |
DOM nodes |
Map, Set |
symbols as values (as property values limited) |
ArrayBuffer, typed arrays |
class instances lose methods (become plain data if cloneable fields only — prototypes not preserved as class) |
| booleans, numbers, strings, bigint | getters that throw, some platform objects |
// DataCloneError
// structuredClone({ fn: () => {} });
// structuredClone(document.body);
Cloned objects are data, not live class instances with methods on the prototype you defined — reconstruct domain objects after clone if needed.
Transferables
const buffer = new ArrayBuffer(1024);
const copy = structuredClone({ buffer }, { transfer: [buffer] });
// buffer is detached in the original side — zero-copy move
buffer.byteLength; // 0
Same transfer list idea as postMessage. Use when shipping large binaries to workers without duplicating memory.
vs alternatives
| Approach | Pros | Cons |
|---|---|---|
structuredClone |
real types, cycles | no functions; availability old browsers |
| JSON round-trip | ubiquitous | loses Date/Map/undefined/cycles |
Lodash cloneDeep |
functions optional modes | bundle size; still can’t clone DOM |
| manual | full control | easy to get wrong |
// polyfill path for older targets: history hack
// const clone = (v) => {
// const mc = new MessageChannel();
// ... async only — structuredClone is sync
// };
Prefer native structuredClone with a small polyfill package only if browserslist demands it.
React state note
setState(structuredClone(state)); // deep copy — heavy
// Usually better: immutable updates at the changed path
Deep cloning entire app state every action is a performance footgun. Use structuredClone for isolated values (worker messages, undo snapshots of a document model).
Interview answer (out loud)
“structuredClone deep-clones data using the HTML structured clone algorithm — Dates, Maps, Sets, cycles, binaries. Functions and DOM nodes throw. You can transfer ArrayBuffers for zero-copy. It’s better than JSON for real data; I still do path-based immutable updates for React state instead of cloning everything.”
Workers and round-trips
// main
worker.postMessage(structuredClone(state)); // often redundant — postMessage clones already
worker.postMessage(state); // clone built-in
Don’t double-clone. Use structuredClone when you need a same-realm deep copy without messaging.
Prototype loss example
class Point {
constructor(x, y) { this.x = x; this.y = y; }
dist() { return Math.hypot(this.x, this.y); }
}
const p = structuredClone(new Point(3, 4));
// p is a plain object {x,y} — no dist method
Re-hydrate: Object.assign(new Point(0,0), p) or a custom fromJSON.
Further reading
Related
- Deep vs Shallow Copy
- postMessage and Origin Checks
- Web Workers Overview
- Typed Arrays and ArrayBuffer
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.