Learn JavaScript

structuredClone() - функция для глубокого копирования значений в JavaScript

structuredClone() создаёт глубокую копию значения.

Это глобальная функция JavaScript, а не метод Object, но чаще всего её используют именно для копирования объектов, массивов и других сложных структур данных.

Синтаксис

structuredClone(value)
structuredClone(value, options)

value - значение, которое нужно скопировать.

options - дополнительные настройки, например transfer для некоторых типов данных.

Что делает structuredClone()

Функция создаёт новую независимую копию значения.

const user = {
  name: "Anna",
  address: {
    city: "Moscow",
  },
};

const copy = structuredClone(user);

copy.address.city = "Paris";

console.log(user.address.city); // "Moscow"
console.log(copy.address.city); // "Paris"

Это главное, что важно запомнить: вложенные объекты здесь тоже копируются, а не остаются общими ссылками.

Что возвращает structuredClone()

structuredClone() возвращает новое значение того же общего типа.

const arr = [1, 2, { a: 3 }];
const copy = structuredClone(arr);

console.log(copy);          // [1, 2, { a: 3 }]
console.log(copy === arr);  // false

Если копируется объект или массив, результатом будет новая независимая структура.

Это глубокая копия

В отличие от spread и Object.assign(), structuredClone() копирует не только верхний уровень.

const user = {
  profile: {
    city: "Moscow",
  },
};

const copy = structuredClone(user);

copy.profile.city = "Paris";

console.log(user.profile.city); // "Moscow"

Поэтому structuredClone() часто выбирают именно тогда, когда поверхностной копии уже недостаточно.

structuredClone() и spread

Spread делает только поверхностную копию:

const user = {
  profile: {
    city: "Moscow",
  },
};

const copy = { ...user };

copy.profile.city = "Paris";

console.log(user.profile.city); // "Paris"

structuredClone() в такой ситуации создаёт уже независимую вложенную копию.

structuredClone() и Object.assign()

С Object.assign() история такая же: это тоже только поверхностное копирование.

const user = {
  profile: {
    city: "Moscow",
  },
};

const copy = Object.assign({}, user);

copy.profile.city = "Paris";

console.log(user.profile.city); // "Paris"

Если нужна именно глубокая копия, structuredClone() подходит лучше.

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

Это одна из сильных сторон structuredClone().

Он умеет копировать структуры, где объект ссылается сам на себя.

const user = { name: "Anna" };
user.self = user;

const copy = structuredClone(user);

console.log(copy.self === copy); // true

Это удобно, потому что многие другие способы копирования на таком месте ломаются.

Date, Map, Set, RegExp

structuredClone() умеет работать не только с обычными объектами и массивами.

Он также клонирует многие встроенные типы:

  • Date
  • Map
  • Set
  • RegExp

Пример:

const date = new Date("2024-01-01T00:00:00Z");
const copy = structuredClone(date);

console.log(copy instanceof Date); // true
console.log(copy.toISOString());   // "2024-01-01T00:00:00.000Z"

Пример с Map:

const map = new Map([["a", 1]]);
const copy = structuredClone(map);

console.log(copy instanceof Map); // true
console.log(copy.get("a"));       // 1

Функции не клонируются

Это важное ограничение.

Если внутри значения есть функция, structuredClone() выбросит ошибку DataCloneError.

const user = {
  name: "Anna",
  greet() {
    console.log("Hi");
  },
};

structuredClone(user); // DataCloneError

Symbol тоже не клонируется как значение

Если попытаться клонировать Symbol, будет ошибка.

structuredClone(Symbol("id")); // DataCloneError

Поэтому structuredClone() подходит не для всех типов данных.

Пользовательские классы и прототипы

Это важный нюанс, о котором легко забыть.

Если клонировать экземпляр пользовательского класса, его прототип обычно не сохраняется как у исходного объекта.

class User {
  constructor(name) {
    this.name = name;
  }
}

const user = new User("Anna");
const copy = structuredClone(user);

console.log(Object.getPrototypeOf(copy) === User.prototype);   // false
console.log(Object.getPrototypeOf(copy) === Object.prototype); // true

То есть по данным копия похожа, но как экземпляр класса она уже не полностью та же самая.

Getter, setter и дескрипторы не сохраняются как есть

structuredClone() копирует данные, а не точную структуру дескрипторов свойства.

Например, getter может превратиться в обычное значение.

const user = {
  first: "Anna",
  last: "Smith",
  get fullName() {
    return `${this.first} ${this.last}`;
  },
};

const copy = structuredClone(user);

console.log(copy.fullName); // "Anna Smith"

Но это уже не обязательно getter в том же виде, что был у исходного объекта.

Если нужна точная копия дескрипторов, обычно используют связку:

Object.defineProperties({}, Object.getOwnPropertyDescriptors(source))

Неперечисляемые и символьные свойства

На такие свойства не стоит рассчитывать как на надёжно сохранённые “один в один”.

structuredClone() в первую очередь ориентирован на перенос данных, а не на точное копирование всех настроек объекта.

Если важны неперечисляемые свойства, символы или тонкая настройка дескрипторов, лучше смотреть в сторону методов работы с дескрипторами.

structuredClone() и JSON

Раньше для копирования часто писали так:

const copy = JSON.parse(JSON.stringify(value));

Но у такого способа есть ограничения:

  • теряются Date, Map, Set, RegExp
  • ломаются циклические ссылки
  • часть данных может потеряться или превратиться в другое

structuredClone() в этом плане надёжнее и современнее.

Передача ownership через transfer

У structuredClone() есть второй аргумент options, где можно использовать transfer.

Это более специальный сценарий для некоторых типов данных вроде ArrayBuffer.

structuredClone(value, {
  transfer: [buffer],
})

Такой вариант нужен реже, но полезно знать, что он существует.

Когда это полезно

structuredClone() удобно использовать, когда:

  • нужна глубокая копия объекта или массива
  • в данных есть вложенные структуры
  • нужно корректно копировать Date, Map, Set, RegExp
  • нужно безопасно работать с циклическими ссылками

Пример:

const settings = {
  theme: "dark",
  user: {
    name: "Anna",
  },
};

const nextSettings = structuredClone(settings);

nextSettings.user.name = "Ivan";

console.log(settings.user.name);     // "Anna"
console.log(nextSettings.user.name); // "Ivan"

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

Думать, что structuredClone() работает как точная копия объекта со всеми дескрипторами

const copy = structuredClone(source);

Метод копирует данные, но не гарантирует сохранение всей низкоуровневой структуры свойства один в один.

Использовать structuredClone() для функций

const value = {
  fn() {},
};

structuredClone(value); // DataCloneError

Функции этот метод не клонирует.

Ждать, что экземпляр класса сохранит свой прототип как есть

class User {
  constructor(name) {
    this.name = name;
  }
}

const user = new User("Anna");
const copy = structuredClone(user);

По данным копия будет похожей, но прототип пользовательского класса может не сохраниться.

Быстрая памятка

  • structuredClone() создаёт глубокую копию значения
  • функция умеет работать с вложенными структурами и циклическими ссылками
  • она поддерживает многие встроенные типы вроде Date, Map, Set, RegExp
  • функции и Symbol как значение не клонируются
  • это глобальная функция, а не метод Object

Коротко

structuredClone() - это современный способ сделать глубокую копию значения в JavaScript. Главное, что стоит запомнить: он хорошо копирует данные и сложные структуры, но не предназначен для точного переноса функций, пользовательских прототипов и всех дескрипторов свойств один в один.