Клонирование объектов является важным аспектом при работе с 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.
Поддержку в браузерах можно просмотреть по ссылке.
Ссылки: