The structuredClone function provides native deep cloning with support for complex types. Here's how to use it effectively.
Basic Usage#
1const original = {
2 name: 'John',
3 age: 30,
4 hobbies: ['reading', 'gaming'],
5 address: {
6 city: 'New York',
7 country: 'USA',
8 },
9};
10
11// Deep clone
12const clone = structuredClone(original);
13
14// Verify independence
15clone.hobbies.push('cooking');
16clone.address.city = 'Boston';
17
18console.log(original.hobbies); // ['reading', 'gaming']
19console.log(original.address.city); // 'New York'vs Other Methods#
1const obj = {
2 date: new Date(),
3 regex: /test/gi,
4 nested: { deep: { value: 1 } },
5 arr: [1, [2, 3]],
6};
7
8// JSON.parse/stringify - loses types
9const jsonClone = JSON.parse(JSON.stringify(obj));
10console.log(jsonClone.date instanceof Date); // false (string)
11console.log(jsonClone.regex instanceof RegExp); // false (empty object)
12
13// Spread - shallow only
14const spreadClone = { ...obj };
15spreadClone.nested.deep.value = 999;
16console.log(obj.nested.deep.value); // 999 (mutated!)
17
18// structuredClone - proper deep clone
19const properClone = structuredClone(obj);
20console.log(properClone.date instanceof Date); // true
21console.log(properClone.regex instanceof RegExp); // true
22properClone.nested.deep.value = 999;
23console.log(obj.nested.deep.value); // 1 (unchanged)Supported Types#
1// All of these are properly cloned
2const supported = {
3 // Primitives
4 string: 'hello',
5 number: 42,
6 bigint: 9007199254740993n,
7 boolean: true,
8 null: null,
9 undefined: undefined,
10
11 // Objects
12 object: { nested: { value: 1 } },
13 array: [1, 2, [3, 4]],
14
15 // Built-in types
16 date: new Date(),
17 regex: /pattern/gi,
18 map: new Map([
19 ['a', 1],
20 ['b', 2],
21 ]),
22 set: new Set([1, 2, 3]),
23
24 // Typed arrays
25 arrayBuffer: new ArrayBuffer(8),
26 int32Array: new Int32Array([1, 2, 3]),
27 uint8Array: new Uint8Array([255, 128, 0]),
28 float64Array: new Float64Array([1.5, 2.5]),
29
30 // Error objects
31 error: new Error('Something went wrong'),
32 typeError: new TypeError('Type mismatch'),
33};
34
35const clone = structuredClone(supported);Unsupported Types#
1// These CANNOT be cloned
2const unsupported = {
3 // Functions
4 fn: () => console.log('hello'),
5 method() {
6 return this.value;
7 },
8
9 // Symbols
10 sym: Symbol('id'),
11
12 // DOM nodes
13 // element: document.body,
14
15 // Class instances (lose prototype)
16 // instance: new MyClass(),
17};
18
19// structuredClone(unsupported.fn); // DataCloneError
20// structuredClone(unsupported.sym); // DataCloneError
21
22// Workaround for class instances
23class User {
24 constructor(name, age) {
25 this.name = name;
26 this.age = age;
27 }
28
29 greet() {
30 return `Hello, ${this.name}`;
31 }
32
33 clone() {
34 return new User(this.name, this.age);
35 }
36}Transfer Instead of Clone#
1// Transfer ownership (zero-copy) for ArrayBuffers
2const buffer = new ArrayBuffer(1024 * 1024); // 1MB
3console.log('Before:', buffer.byteLength); // 1048576
4
5// Clone with transfer
6const clone = structuredClone(
7 { data: buffer },
8 { transfer: [buffer] }
9);
10
11console.log('Original after:', buffer.byteLength); // 0 (detached)
12console.log('Clone:', clone.data.byteLength); // 1048576
13
14// Useful for large data
15const largeData = new Float32Array(1000000);
16const transferred = structuredClone(
17 { array: largeData.buffer },
18 { transfer: [largeData.buffer] }
19);Circular References#
1// structuredClone handles circular references
2const circular = { name: 'Object' };
3circular.self = circular;
4
5const clone = structuredClone(circular);
6
7console.log(clone.self === clone); // true
8console.log(clone.self !== circular); // true
9
10// Complex circular
11const a = { name: 'A' };
12const b = { name: 'B', ref: a };
13a.ref = b;
14
15const clonedA = structuredClone(a);
16console.log(clonedA.ref.ref === clonedA); // truePractical Examples#
1// State management - immutable updates
2function updateState(state, path, value) {
3 const newState = structuredClone(state);
4 let current = newState;
5
6 for (let i = 0; i < path.length - 1; i++) {
7 current = current[path[i]];
8 }
9
10 current[path[path.length - 1]] = value;
11 return newState;
12}
13
14const state = {
15 user: { name: 'John', settings: { theme: 'dark' } },
16};
17
18const newState = updateState(state, ['user', 'settings', 'theme'], 'light');
19
20// Undo/redo history
21class History {
22 constructor(initialState) {
23 this.states = [structuredClone(initialState)];
24 this.index = 0;
25 }
26
27 push(state) {
28 this.states = this.states.slice(0, this.index + 1);
29 this.states.push(structuredClone(state));
30 this.index++;
31 }
32
33 undo() {
34 if (this.index > 0) {
35 this.index--;
36 return structuredClone(this.states[this.index]);
37 }
38 return null;
39 }
40
41 redo() {
42 if (this.index < this.states.length - 1) {
43 this.index++;
44 return structuredClone(this.states[this.index]);
45 }
46 return null;
47 }
48}Form State Snapshot#
1function createFormSnapshot(formData) {
2 return structuredClone({
3 values: formData,
4 timestamp: new Date(),
5 id: crypto.randomUUID(),
6 });
7}
8
9function compareSnapshots(a, b) {
10 return JSON.stringify(a.values) === JSON.stringify(b.values);
11}
12
13// Auto-save with dirty checking
14let lastSnapshot = null;
15
16function checkForChanges(currentData) {
17 const current = createFormSnapshot(currentData);
18
19 if (!lastSnapshot || !compareSnapshots(lastSnapshot, current)) {
20 saveToServer(current);
21 lastSnapshot = current;
22 }
23}Worker Communication#
1// Main thread
2const worker = new Worker('worker.js');
3
4const largeData = {
5 matrix: new Float64Array(1000000),
6 metadata: { rows: 1000, cols: 1000 },
7};
8
9// Clone data to send (or use transfer)
10worker.postMessage(structuredClone(largeData));
11
12// Worker thread (worker.js)
13self.onmessage = (event) => {
14 const data = event.data;
15 // data is already cloned by postMessage
16 // Process data...
17 const result = processMatrix(data.matrix);
18 self.postMessage({ result });
19};Deep Freeze with Clone#
1function deepFreeze(obj) {
2 // Clone first to avoid modifying original
3 const clone = structuredClone(obj);
4
5 Object.keys(clone).forEach((key) => {
6 if (
7 typeof clone[key] === 'object' &&
8 clone[key] !== null &&
9 !Object.isFrozen(clone[key])
10 ) {
11 deepFreeze(clone[key]);
12 }
13 });
14
15 return Object.freeze(clone);
16}
17
18const config = deepFreeze({
19 api: { url: 'https://api.example.com' },
20 features: ['a', 'b', 'c'],
21});
22
23// config.api.url = 'new'; // Error in strict modePerformance Comparison#
1const testObject = {
2 users: Array.from({ length: 1000 }, (_, i) => ({
3 id: i,
4 name: `User ${i}`,
5 email: `user${i}@example.com`,
6 metadata: { created: new Date(), tags: ['a', 'b'] },
7 })),
8};
9
10// Benchmark
11console.time('structuredClone');
12for (let i = 0; i < 100; i++) {
13 structuredClone(testObject);
14}
15console.timeEnd('structuredClone');
16
17console.time('JSON');
18for (let i = 0; i < 100; i++) {
19 JSON.parse(JSON.stringify(testObject));
20}
21console.timeEnd('JSON');
22
23// structuredClone is often faster and more correctError Handling#
1function safeClone(obj) {
2 try {
3 return { success: true, data: structuredClone(obj) };
4 } catch (error) {
5 if (error instanceof DOMException) {
6 // DataCloneError - contains non-cloneable value
7 return {
8 success: false,
9 error: 'Object contains non-cloneable values',
10 details: error.message,
11 };
12 }
13 throw error;
14 }
15}
16
17// Check if value is cloneable
18function isCloneable(value) {
19 try {
20 structuredClone(value);
21 return true;
22 } catch {
23 return false;
24 }
25}
26
27console.log(isCloneable({ a: 1 })); // true
28console.log(isCloneable(() => {})); // false
29console.log(isCloneable(Symbol('x'))); // falseBest Practices#
When to Use:
✓ Deep cloning objects
✓ Preserving built-in types
✓ Handling circular refs
✓ State snapshots
✓ Worker communication
Supported Types:
✓ Objects, Arrays
✓ Date, RegExp
✓ Map, Set
✓ ArrayBuffer, TypedArrays
✓ Error objects
Not Supported:
✗ Functions
✗ Symbols
✗ DOM nodes
✗ Class prototypes
Performance:
✓ Faster than JSON for complex objects
✓ Use transfer for large ArrayBuffers
✓ Consider lazy cloning for huge objects
Conclusion#
The structuredClone function provides native deep cloning with proper handling of Date, RegExp, Map, Set, ArrayBuffer, and circular references. Use it instead of JSON.parse/stringify for accurate type preservation and instead of spread for true deep copies. Transfer ArrayBuffers for zero-copy performance with large data. Remember that functions, Symbols, and DOM nodes cannot be cloned - handle these cases with custom clone methods or alternatives.