Back to Blog
JavaScriptstructuredCloneDeep CloneObjects

JavaScript structuredClone Guide

Master JavaScript structuredClone for deep cloning objects with proper handling of complex types.

B
Bootspring Team
Engineering
February 24, 2019
6 min read

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); // true

Practical 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 mode

Performance 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 correct

Error 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'))); // false

Best 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.

Share this article

Help spread the word about Bootspring