Структурированное клонирование: structuredClone

structuredClone — встроенная функция (ES2022 / Node 17+), создающая глубокую копию объекта с поддержкой большинства встроенных типов (Date, Map, Set, ArrayBuffer, RegExp) в отличие от JSON.parse(JSON.stringify(...)).

Зачем нужно

Глубокое копирование объектов — частая задача: нужно изменить вложенные данные не затрагивая оригинал (Redux, иммутабельные обновления). JSON.parse/stringify не копирует Date, Map, Set, undefined, функции, циклические ссылки. structuredClone решает все эти проблемы нативно.

Где используется

  • Иммутабельные обновления состояния (Redux, Zustand)
  • Передача данных между Web Workers
  • Глубокое копирование конфигурационных объектов
  • Клонирование данных API-ответов перед модификацией

Базовое использование

const original = {
  name: 'Иван',
  address: {
    city: 'Москва',
    coords: [55.75, 37.61]
  },
  tags: ['admin', 'user']
};

const clone = structuredClone(original);

clone.address.city = 'СПб';
clone.tags.push('moderator');

console.log(original.address.city); // 'Москва' — не изменился
console.log(original.tags);         // ['admin', 'user'] — не изменился

Поддерживаемые типы

// Date
const withDate = { created: new Date('2024-01-01') };
const clonedDate = structuredClone(withDate);
console.log(clonedDate.created instanceof Date); // true — правильный тип!

// JSON.parse/stringify:
const jsonClone = JSON.parse(JSON.stringify(withDate));
console.log(typeof jsonClone.created); // 'string' — дата превратилась в строку!

// Map и Set
const withMap = { lookup: new Map([['a', 1], ['b', 2]]) };
const clonedMap = structuredClone(withMap);
console.log(clonedMap.lookup instanceof Map); // true

const withSet = { unique: new Set([1, 2, 3]) };
const clonedSet = structuredClone(withSet);
console.log(clonedSet.unique instanceof Set); // true

// RegExp
const withRegex = { pattern: /hello/gi };
const clonedRegex = structuredClone(withRegex);
console.log(clonedRegex.pattern instanceof RegExp); // true

// ArrayBuffer и TypedArray
const buffer = new ArrayBuffer(16);
const view   = new Int32Array(buffer);
view[0] = 42;

const clonedBuffer = structuredClone(buffer);
// clonedBuffer — независимая копия

Циклические ссылки

const obj = { name: 'Тест' };
obj.self = obj; // циклическая ссылка

// JSON.parse/stringify:
// JSON.stringify(obj); // TypeError: Converting circular structure to JSON

// structuredClone обрабатывает корректно!
const clone = structuredClone(obj);
console.log(clone.self === clone); // true — связь сохранена в копии

Передача между Workers (Transferable)

// Второй аргумент: { transfer: [...] } — перемещение (не копирование) буферов
const buffer = new ArrayBuffer(1024);

// Передача с перемещением — buffer становится пустым после этого
const clone = structuredClone(buffer, { transfer: [buffer] });
console.log(buffer.byteLength); // 0 — буфер перемещён, не скопирован

Что НЕ поддерживается

// Функции
const withFn = { fn:  => 'hello' };
structuredClone(withFn); // DataCloneError: fn could not be cloned

// DOM-узлы
structuredClone(document.body); // DataCloneError

// WeakMap, WeakSet
structuredClone(new WeakMap); // DataCloneError

// Прототипы не сохраняются для пользовательских классов
class Point { constructor(x, y) { this.x = x; this.y = y; } }
const p = new Point(1, 2);
const cloned = structuredClone(p);
console.log(cloned instanceof Point); // false! — стал обычным объектом

Сравнение методов глубокого клонирования

Метод Date Map/Set Функции Циклические Классы
JSON.parse/stringify Нет Нет Нет Нет Нет
structuredClone Да Да Нет Да Частично
Lodash _.cloneDeep Да Да Нет Да Да

Частые ошибки

1. Клонирование классовых экземпляров

class User {
  constructor(name) { this.name = name; }
  greet { return `Привет, ${this.name}`; }
}

const u = new User('Анна');
const clone = structuredClone(u);
clone.greet; // TypeError: clone.greet is not a function — метод потерян!

// Решение: ручное клонирование через конструктор
const cloned = new User(u.name);

2. Ошибка с функциями в объектах (молча теряются)

В некоторых средах функции просто игнорируются, в других — выбрасывается DataCloneError. Проверяй структуру перед клонированием.

Связанные темы

Ресурсы