Поверхностное и глубокое копирование в JavaScript
Прежде чем перейти к подробному рассмотрению способов копирования данных, давайте сперва разберемся, в чем разница между передачей данных по значению и по ссылке.
Как мы знаем, в JavaScript данные в переменную передаются либо по значению, либо по ссылке. В первом случае по значению передаются только примитивы. К ним относятся такие типы, как строка, число, булево значение, символ и т.д. А во втором случае по ссылке передаются объекты.
Что значит передача по значению? Это когда в переменной записывается конкретное значение, которое имеет свой уникальный адрес в памяти.
let user1 = "Ivan";
let user2 = "Alex";
Также мы можем скопировать в переменную user2 другую переменную user1. И обе эти переменные будут иметь уникальные адреса в памяти и между собой никак не будут связаны.
В случае передачи по ссылке при копировании объекта мы лишь присваиваем ссылку на данные, то есть назначаем адрес памяти объекта оригинальной переменной.
Поверхностное копирование
Рассмотрим способы поверхностного копирования объектов.
1. Самый короткий и простой способ скопировать объект — это использовать синтаксис spread.
const person = { name: "Ivan", age: 23 };
const clonedPerson = { ...person }; // Object { name: "Ivan", age: 23 }
2. Также для копирования объекта можно использовать метод Object.assign().
const person = { name: "Ivan", age: 23 };
const clonedPerson = Object.assign({}, person); // Object { name: "Ivan", age: 23 }
Проблема с этими двумя подходами заключается в том, что они просто создают новый объект, который имеет точную копию значений в исходном объекте. Но, если любое из полей объекта является ссылкой на другие объекты, копируются только ссылочные адреса, то есть копируется только адрес памяти. Изменение такого поля ссылки будет отражено в обоих объектах.
Получается, что эти два метода являются полезными, но они не сработают, когда у вас есть вложенный объект для копирования.
Глубокое копирование
Теперь давайте рассмотрим современные способы глубокого копирования объектов.
1. В JavaScript есть функция structuredClone() для глубокого копирования массивов или объектов. Она доступна в NodeJS начиная с версии 17.0.0. StructuredClone может клонировать бесконечно вложенные объекты и массивы, циклические ссылки, широкий спектр типов JavaScript, такие как Date, Set, Map, Error, RegExp, ArrayBuffer, Blob, File, ImageData и многие другие. Однако structuredClone не копирует функции, DOM-узлы и дескрипторы свойств.
2. Применение комбинации JSON.parse() и JSON.stringify(). На самом деле это отличный способ и на удивление производительный, но есть некоторые нюансы. Такой метод может обрабатывать только объекты базовых типов, поэтому, например, new Date превратится в [object Object].
3. Использование функции cloneDeep библиотеки Lodash. Отметим, что cloneDeep работает на основе рекурсивного обхода объекта, а это «дорогая операция», поэтому лучше использовать ее, когда это действительно необходимо. К тому же мы всегда можем написать подобную функцию самостоятельно.
Заключение
При создании копии нужно убедиться, хотим ли мы обновлять оригинал при изменении этой копии или нет.
Если нам нужно обновлять оригинал, мы можем применять поверхностную копию, которая следует концепции передачи по ссылке. В противном случае можем использовать глубокую копию, которая следует концепции передачи по значению.
Нужно понимать, что глубокое копирование может сильно влиять на производительность, поэтому используйте его, лишь когда это действительно необходимо.