Подробный обзор функции structuredClone: назначение, поддерживаемые типы, ограничения и отличия от JSON-клонирования и других подходов.

Функция structuredClone

Копирование объектов — задача, которая кажется тривиальной, пока в структуре не появятся вложенные ссылки, циклические зависимости или типы данных вроде Map, Date и ArrayBuffer. Функция structuredClone решает именно эту задачу на уровне платформы.

Долгое время в JavaScript не было встроенного способа сделать глубокую копию (deep clone) произвольного значения. Разработчики либо писали рекурсивные обходы вручную, либо использовали трюк JSON.parse(JSON.stringify(value)), либо подключали сторонние библиотеки вроде Lodash. У каждого из этих решений были свои ограничения: JSON-подход терял функции, undefined, Date, Map, Set и не умел работать с циклическими ссылками; ручные обходы легко ломались на экзотических типах.

Функция structuredClone появилась как стандартный ответ на эту проблему. Она выполняет структурное клонирование — алгоритм, который уже много лет использовался браузерами при передаче данных через postMessage, при работе с IndexedDB и в других API. Теперь тот же механизм доступен напрямую как глобальная функция.

Что такое структурное клонирование

Структурное клонирование (structured clone) — это алгоритм, который создаёт глубокую копию значения, проходя по его внутренней структуре. В отличие от поверхностного копирования через Object.assign или spread-оператор, он копирует не только верхний уровень, но и все вложенные объекты, массивы и другие контейнеры. При этом сохраняются ссылочные связи: если один и тот же объект встречается в структуре дважды, в копии он тоже будет представлен одним объектом, а не двумя разными.

Алгоритм корректно обрабатывает циклические ссылки. Например, объект, который ссылается сам на себя, будет скопирован без бесконечной рекурсии.

const original = { name: "узел" };
original.self = original;

const copy = structuredClone(original);
console.log(copy.self === copy); // true
console.log(copy === original);   // false

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

Структурное клонирование умеет работать с большинством встроенных типов, которые встречаются в обычном коде. Важно понимать, что именно копируется по значению, а что не поддерживается вовсе.

Поддерживаются:

  • примитивы: string, number, boolean, null, undefined, bigint;
  • Date, RegExp;
  • Map, Set, WeakMap (только как значение, не как ключ), WeakSet;
  • ArrayBuffer, TypedArray, DataView;
  • Blob, File, FileList, ImageData;
  • простые объекты и массивы;
  • Error и его подтипы.

Не поддерживаются и приводят к исключению DataCloneError:

  • функции;
  • DOM-узлы;
  • свойства-аксессоры (геттеры и сеттеры) — копируется только значение;
  • прототипы объектов — копия получает Object.prototype или Array.prototype, даже если оригинал был экземпляром пользовательского класса;
  • символы в качестве значений свойств;
  • WeakRef, FinalizationRegistry.
Структурное клонирование — это копирование данных, а не поведения. Всё, что связано с логикой (функции, методы, геттеры), в копию не переносится.

Отличия от JSON-клонирования

Самый распространённый «народный» способ глубокого копирования — прогнать значение через JSON. Этот приём работает, но молча теряет данные, что делает его опасным в нетривиальных структурах.

const source = {
  date: new Date(),
  map: new Map([["k", 1]]),
  undef: undefined,
  fn: () => {}
};

const jsonCopy = JSON.parse(JSON.stringify(source));
// date — строка, а не Date
// map — пустой объект {}
// undef — свойство исчезло
// fn — свойство исчезло

const structuredCopy = structuredClone(source);
// date — Date
// map — Map
// undef — undefined
// fn — выбросит DataCloneError

Разница принципиальная: JSON-подход преобразует данные в текстовый формат и обратно, теряя всё, что не выражается в JSON. Структурное клонирование работает на уровне внутренних представлений объектов и сохраняет типы. Однако за это приходится платить: наличие функции в структуре приводит к ошибке, а не к тихому пропуску.

Синтаксис и возвращаемое значение

Функция принимает одно обязательное значение и возвращает его глубокую копию.

const clone = structuredClone(value);

// с опцией передачи ресурсов (например, ArrayBuffer):
const clone2 = structuredClone(value, { transfer: [buffer] });

Опция transfer позволяет передать владение ArrayBuffer из оригинала в копию, а не копировать его. После передачи исходный буфер становится недоступным (его byteLength равен нулю). Это полезно для больших бинарных данных, когда копирование избыточно.

Функция синхронная, не принимает колбэков и не возвращает промис. При ошибке выбрасывает исключение DataCloneError (наследник DOMException).

Ограничения и типичные ошибки

Главное ограничение — невозможность клонировать функции, DOM-узлы и объекты с кастомными прототипами. Второе, менее очевидное: копия не сохраняет связь с классом оригинала.

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

const u = new User("Анна");
const c = structuredClone(u);

console.log(c instanceof User); // false
console.log(c.greet);           // undefined

Если требуется сохранить поведение, структурное клонирование не подходит — нужно либо реализовать собственный метод копирования, либо использовать паттерн «клонирование через конструктор». Также стоит помнить, что свойства-аксессоры в копии превращаются в обычные свойства со значением, полученным в момент клонирования.

Когда использовать structuredClone

Функция уместна в ситуациях, когда нужно получить независимую копию данных, состоящих из стандартных типов: конфигураций, снимков состояния, сообщений, результатов запросов. Она хорошо подходит для передачи данных между воркерами, для сохранения снимков перед мутацией и для отката изменений.

Не стоит применять её к объектам с методами, к DOM-структурам и к значениям, где важна идентичность класса. В этих случаях разумнее явно описать логику копирования.

Что запомнить

  • structuredClone — встроенная глобальная функция для глубокого копирования значений.
  • Она использует алгоритм структурного клонирования, тот же, что применяется в postMessage и IndexedDB.
  • Корректно обрабатывает циклические ссылки и сохраняет разделяемые ссылки внутри структуры.
  • Поддерживает Date, Map, Set, ArrayBuffer, TypedArray, Error и другие стандартные типы.
  • Не клонирует функции, DOM-узлы, символы и объекты с пользовательскими прототипами — выбрасывает DataCloneError.
  • Не сохраняет прототип: копия экземпляра класса становится простым объектом.
  • Опция transfer позволяет передать владение ArrayBuffer вместо копирования.
  • Предпочтительнее JSON.parse(JSON.stringify(...)), так как не теряет типы и не игнорирует ошибки.
  • Не заменяет пользовательскую логику копирования там, где важно поведение объекта.