Способы копирования объекта в js

Клонирование объектов является важным аспектом при работе с JavaScript. Существует несколько способов клонирования объектов. Рассмотрим популярные варианты клонирования.

Поверхностное клонирование через Object.assign

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

const obj1 = {a: 1, b: {c: 2}};
const obj2 = Object.assign({}, obj1);

obj1.b.c = 3;

console.log(obj2.b.c); // 3

Поверхностное клонирование спред оператором

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

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

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

myShallowCopy.anotherProp.aNewProp = "a new value";

console.log(myOriginal.anotherProp.aNewProp)  // `a new value`

Глубокое клонирование с помощью JSON

Противоположностью поверхностной копии является глубокая копия. Алгоритм глубокого копирования также копирует свойства объекта по одному, но вызывает себя рекурсивно, когда находит ссылку на другой объект, создавая копию и этого объекта. Это может быть очень важно для того, чтобы убедиться, что два фрагмента кода случайно не разделят объект и не будут неосознанно манипулировать состоянием друг друга.

Раньше не существовало простого и удобного способа создания глубокой копии значения в JavaScript. Многие полагались на сторонние библиотеки, такие как функция Lodash cloneDeep(). Пожалуй, самым распространенным решением этой проблемы был хак на основе JSON.

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

const obj1 = {a: 1, b: {c: 2}};
const obj2 = JSON.parse(JSON.stringify(obj1));

obj1.b.c = 3;

console.log(obj2.b.c); // 2

Это был настолько популярный обходной путь, что V8 агрессивно оптимизировал JSON.parse() и, в частности, вышеприведенный паттерн, чтобы сделать его как можно быстрым.

Клонирование с помощью функции structuredClone

Структурированная клонирование - это механизм, позволяющий копировать объекты JavaScript, включая циклические ссылки и объекты, которые не могут быть сериализованы с помощью JSON.

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

const kitchenSink = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [ new File(someBlobData, 'file.txt') ] },
  error: new Error('Hello!')
};

kitchenSink.circular = kitchenSink;

const clonedSink = structuredClone(kitchenSink);

Если требуется полифил для поддержки старых браузеров, тогда необходимо использовать библиотеку @ungap/structured-clone:

import structuredClone from '@ungap/structured-clone';

Возможности и ограничения

Структурированное клонирование устраняет многие (хотя и не все) недостатки метода JSON.stringify(). Структурированное клонирование может работать с циклическими структурами данных, поддерживает множество встроенных типов данных и, как правило, является более надежным и часто более быстрым.

Тем не менее, у него все еще есть некоторые ограничения, которые могут застать вас врасплох:

  • Прототипы: если вы используете structuredClone() с экземпляром класса, то в качестве возвращаемого значения вы получите обычный объект, поскольку при структурированном клонировании цепочка прототипов объекта отбрасывается.
  • Функции: если ваш объект содержит функции, они будут тихо отброшены.
  • Неклонируемые: некоторые значения не подлежат структурированному клонированию, в частности, Error и узлы DOM. Это приведет к выбросу structuredClone().

Если какое-либо из этих ограничений является препятствием для вашего варианта использования, такие библиотеки, как Lodash, по-прежнему предоставляют пользовательские реализации других алгоритмов глубокого клонирования, которые могут соответствовать или не соответствовать вашему варианту использования.

Заключение

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

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


Ссылки: