Back to Blog
JavaScriptstructuredCloneDeep CloneES2022

JavaScript structuredClone API Guide

Master the JavaScript structuredClone API for deep cloning objects, arrays, and complex data structures.

B
Bootspring Team
Engineering
January 6, 2020
6 min read

The structuredClone function creates deep copies of values using the structured clone algorithm. Here's how to use it.

Basic Usage#

1// Deep clone objects 2const original = { 3 name: 'Alice', 4 address: { 5 city: 'New York', 6 zip: '10001', 7 }, 8 hobbies: ['reading', 'coding'], 9}; 10 11const clone = structuredClone(original); 12 13// Modifications don't affect original 14clone.name = 'Bob'; 15clone.address.city = 'Boston'; 16clone.hobbies.push('gaming'); 17 18console.log(original.name); // 'Alice' 19console.log(original.address.city); // 'New York' 20console.log(original.hobbies); // ['reading', 'coding']

Supported Types#

1// Primitives 2structuredClone('hello'); // 'hello' 3structuredClone(42); // 42 4structuredClone(true); // true 5structuredClone(null); // null 6structuredClone(undefined); // undefined 7 8// Objects and Arrays 9structuredClone({ a: 1 }); // { a: 1 } 10structuredClone([1, 2, 3]); // [1, 2, 3] 11 12// Date objects 13const date = new Date(); 14const clonedDate = structuredClone(date); 15console.log(clonedDate instanceof Date); // true 16console.log(clonedDate.getTime() === date.getTime()); // true 17 18// Regular expressions 19const regex = /hello/gi; 20const clonedRegex = structuredClone(regex); 21console.log(clonedRegex instanceof RegExp); // true 22console.log(clonedRegex.source); // 'hello' 23console.log(clonedRegex.flags); // 'gi' 24 25// Map and Set 26const map = new Map([['a', 1], ['b', 2]]); 27const clonedMap = structuredClone(map); 28console.log(clonedMap.get('a')); // 1 29 30const set = new Set([1, 2, 3]); 31const clonedSet = structuredClone(set); 32console.log(clonedSet.has(2)); // true 33 34// ArrayBuffer and TypedArrays 35const buffer = new ArrayBuffer(8); 36const view = new Uint8Array(buffer); 37view[0] = 255; 38 39const clonedBuffer = structuredClone(buffer); 40const clonedView = new Uint8Array(clonedBuffer); 41console.log(clonedView[0]); // 255

Circular References#

1// Handles circular references automatically 2const obj = { name: 'circular' }; 3obj.self = obj; 4 5const clone = structuredClone(obj); 6console.log(clone.self === clone); // true 7console.log(clone.self !== obj); // true 8 9// Complex circular structure 10const parent = { name: 'parent', children: [] }; 11const child1 = { name: 'child1', parent }; 12const child2 = { name: 'child2', parent }; 13parent.children.push(child1, child2); 14 15const clonedParent = structuredClone(parent); 16console.log(clonedParent.children[0].parent === clonedParent); // true

Non-Cloneable Types#

1// Functions cannot be cloned 2try { 3 structuredClone({ fn: () => {} }); 4} catch (e) { 5 console.log(e.message); // DataCloneError 6} 7 8// Symbols cannot be cloned 9try { 10 structuredClone({ sym: Symbol('test') }); 11} catch (e) { 12 console.log(e.message); // DataCloneError 13} 14 15// DOM nodes cannot be cloned 16try { 17 structuredClone(document.body); 18} catch (e) { 19 console.log(e.message); // DataCloneError 20} 21 22// Property descriptors are not preserved 23const obj = {}; 24Object.defineProperty(obj, 'readonly', { 25 value: 42, 26 writable: false, 27}); 28 29const clone = structuredClone(obj); 30Object.getOwnPropertyDescriptor(clone, 'readonly').writable; // true 31 32// Prototype chain is not preserved 33class Person { 34 constructor(name) { 35 this.name = name; 36 } 37 greet() { 38 return `Hello, ${this.name}`; 39 } 40} 41 42const person = new Person('Alice'); 43const clonedPerson = structuredClone(person); 44 45console.log(clonedPerson.name); // 'Alice' 46console.log(clonedPerson.greet); // undefined 47console.log(clonedPerson instanceof Person); // false

Transfer Option#

1// Transfer ownership of ArrayBuffers 2const buffer = new ArrayBuffer(1024); 3const view = new Uint8Array(buffer); 4view[0] = 42; 5 6// Transfer instead of clone 7const clonedBuffer = structuredClone(buffer, { transfer: [buffer] }); 8 9console.log(buffer.byteLength); // 0 (detached) 10console.log(clonedBuffer.byteLength); // 1024 11 12// Transfer multiple transferables 13const buffer1 = new ArrayBuffer(100); 14const buffer2 = new ArrayBuffer(200); 15 16const obj = { 17 data1: buffer1, 18 data2: buffer2, 19}; 20 21const clone = structuredClone(obj, { 22 transfer: [buffer1, buffer2], 23}); 24 25// Original buffers are now detached 26console.log(buffer1.byteLength); // 0 27console.log(buffer2.byteLength); // 0

Comparison with Other Methods#

1const original = { 2 name: 'test', 3 nested: { value: 42 }, 4 date: new Date(), 5 regex: /pattern/g, 6 map: new Map([['key', 'value']]), 7}; 8 9// JSON.parse/stringify - loses types 10const jsonClone = JSON.parse(JSON.stringify(original)); 11console.log(jsonClone.date instanceof Date); // false (string) 12console.log(jsonClone.regex instanceof RegExp); // false (object) 13console.log(jsonClone.map instanceof Map); // false (object) 14 15// Spread operator - shallow only 16const spreadClone = { ...original }; 17spreadClone.nested.value = 100; 18console.log(original.nested.value); // 100 (modified!) 19 20// Object.assign - shallow only 21const assignClone = Object.assign({}, original); 22assignClone.nested.value = 200; 23console.log(original.nested.value); // 200 (modified!) 24 25// structuredClone - deep clone with types 26const deepClone = structuredClone(original); 27deepClone.nested.value = 300; 28console.log(original.nested.value); // 42 (unchanged) 29console.log(deepClone.date instanceof Date); // true 30console.log(deepClone.regex instanceof RegExp); // true 31console.log(deepClone.map instanceof Map); // true

Practical Examples#

1// State management 2class Store { 3 constructor(initialState) { 4 this.state = initialState; 5 this.history = [structuredClone(initialState)]; 6 } 7 8 setState(updater) { 9 this.state = updater(structuredClone(this.state)); 10 this.history.push(structuredClone(this.state)); 11 } 12 13 undo() { 14 if (this.history.length > 1) { 15 this.history.pop(); 16 this.state = structuredClone(this.history[this.history.length - 1]); 17 } 18 } 19} 20 21// Form data handling 22function handleFormSubmit(formData) { 23 // Clone to avoid mutations 24 const data = structuredClone(formData); 25 26 // Sanitize 27 data.email = data.email.toLowerCase().trim(); 28 29 // Original formData unchanged 30 return submitToAPI(data); 31} 32 33// Cache with deep copies 34class DeepCache { 35 constructor() { 36 this.cache = new Map(); 37 } 38 39 set(key, value) { 40 this.cache.set(key, structuredClone(value)); 41 } 42 43 get(key) { 44 const value = this.cache.get(key); 45 return value ? structuredClone(value) : undefined; 46 } 47} 48 49// Immutable updates 50function updateUser(user, updates) { 51 const clone = structuredClone(user); 52 return Object.assign(clone, updates); 53}

Error Handling#

1// Graceful handling of non-cloneable values 2function safeClone(value) { 3 try { 4 return structuredClone(value); 5 } catch (e) { 6 if (e.name === 'DataCloneError') { 7 console.warn('Value contains non-cloneable data:', e.message); 8 return fallbackClone(value); 9 } 10 throw e; 11 } 12} 13 14// Custom fallback 15function fallbackClone(value) { 16 // Handle functions by converting to null 17 return JSON.parse( 18 JSON.stringify(value, (key, val) => { 19 if (typeof val === 'function') return null; 20 if (typeof val === 'symbol') return null; 21 return val; 22 }) 23 ); 24} 25 26// Check if value is cloneable 27function isCloneable(value) { 28 try { 29 structuredClone(value); 30 return true; 31 } catch { 32 return false; 33 } 34}

Performance Considerations#

1// structuredClone is slower than shallow methods 2// Use appropriately based on needs 3 4// For simple shallow copies, use spread 5const shallowCopy = { ...obj }; 6 7// For deep nested structures, use structuredClone 8const deepCopy = structuredClone(obj); 9 10// For JSON-safe data, JSON methods may be faster 11const jsonCopy = JSON.parse(JSON.stringify(obj)); 12 13// Benchmark 14function benchmark(fn, iterations = 1000) { 15 const start = performance.now(); 16 for (let i = 0; i < iterations; i++) { 17 fn(); 18 } 19 return performance.now() - start; 20} 21 22const testObj = { a: { b: { c: { d: 1 } } } }; 23 24console.log('Spread:', benchmark(() => ({ ...testObj }))); 25console.log('JSON:', benchmark(() => JSON.parse(JSON.stringify(testObj)))); 26console.log('structuredClone:', benchmark(() => structuredClone(testObj)));

Best Practices#

Usage: ✓ Use for deep cloning objects ✓ Use for immutable state updates ✓ Use when preserving types matters ✓ Use for complex nested structures Benefits: ✓ Handles circular references ✓ Preserves Date, RegExp, Map, Set ✓ Native browser/Node.js support ✓ No external dependencies Limitations: ✗ Cannot clone functions ✗ Cannot clone Symbols ✗ Cannot clone DOM nodes ✗ Loses prototype chain Avoid: ✗ Using for shallow copies ✗ Cloning objects with functions ✗ Performance-critical loops ✗ When JSON suffices

Conclusion#

The structuredClone API provides native deep cloning with support for complex types like Date, RegExp, Map, and Set. Use it for immutable state management, caching, and any scenario requiring true deep copies. Remember it cannot clone functions, symbols, or DOM nodes, and consider performance for hot paths.

Share this article

Help spread the word about Bootspring