Поверхностная и глубокая копия
Запись { ...user } создаёт новый объект и переносит в него значения свойств. Пока значениями были строки и числа, копию можно было менять независимо от оригинала.
Но значением свойства бывает и другой объект:
const user = { name: 'Ира', settings: { theme: 'dark', },};const copy = { ...user };console.log(user === copy);Объектов действительно два. Но изменим вложенный объект:
copy.settings.theme = 'light';console.log(user.settings.theme);Почему снова изменился оригинал?
const user = { name: 'Ира', settings: { theme: 'dark', },};Посмотрим, какие значения лежат в свойствах user:
name→'Ира'– строкаsettings→ ссылка на объект{ theme: 'dark' }
Когда spread переносит свойства, строка 'Ира' копируется как обычное значение, а в settings копируется ссылка на уже существующий вложенный объект. Получается такая структура:
Проследим это по шагам:
user ведёт к объекту, а его свойство settings – к вложенному объекту.Такая копия называется поверхностной: создаётся новый внешний объект, но вложенные объекты автоматически не копируются. Если свойство содержит ссылку на объект, в копию попадёт та же ссылка.
Поэтому user !== copy, но user.:
const user = { name: 'Ира', settings: { theme: 'dark', },};const copy = { ...user };console.log(user === copy);console.log(user.settings === copy.settings);Если settings тоже нужно менять независимо, для него надо создать новый объект:
const user = { name: 'Ира', settings: { theme: 'dark', },};const copy = { ...user, settings: { ...user.settings, },};Здесь создаются уже два новых объекта: внешний { ... } создаёт новый copy, а внутренний { ...user. – отдельный объект settings.
Теперь copy. больше не связан с user.. Поэтому изменение copy. не затрагивает исходный объект:
const user = { name: 'Ира', settings: { theme: 'dark', },};const copy = { ...user, settings: { ...user.settings, },};copy.settings.theme = 'light';console.log(user.settings.theme);console.log(copy.settings.theme);console.log(user.settings === copy.settings);А если вложенность ещё глубже?
Допустим, внутри settings лежит ещё один объект:
const user = { name: 'Ира', settings: { theme: { mode: 'dark', }, },};const copy = { ...user, settings: { ...user.settings, },};Такая копия создаёт новые объекты только для верхнего уровня и для settings, а theme снова остаётся общим. Чтобы сделать независимым и его, пришлось бы продолжить:
const copy = { ...user, settings: { ...user.settings, theme: { ...user.settings.theme, }, },};Чем глубже структура, тем неудобнее делать это вручную.
Поверхностная копия создаёт новый внешний объект, но не копирует вложенные объекты. Поэтому часть структуры может оставаться общей с оригиналом.
Глубокая копия создаёт новые объекты на всех уровнях вложенности. В результате изменение вложенной части копии не изменяет соответствующую часть оригинала.
В сравнениях это выглядит так:
// поверхностнаяuser !== copy;user.settings === copy.settings;// глубокаяuser !== copy;user.settings !== copy.settings;Для многих обычных структур современный JavaScript умеет создать глубокую копию сам:
const user = { name: 'Ира', settings: { theme: 'dark', },};const copy = structuredClone(user);copy.settings.theme = 'light';console.log(user.settings.theme);console.log(copy.settings.theme);structuredClone() умеет глубоко копировать многие обычные структуры данных, но не любые: если в свойстве лежит функция, вызов завершится ошибкой DataCloneError.
Выбор способа сводится к двум вопросам: есть ли в объекте вложенные объекты и должны ли они стать независимыми от оригинала. Если вложенных объектов нет или они могут остаться общими, достаточно поверхностной копии. Если они должны стать независимыми, нужна глубокая копия.
Если нужна поверхностная копия
Основной способ, который вы будете использовать:
const copy = { ...user };Также существуют Object. и ручной перенос свойств циклом for...in – их полезно было увидеть, чтобы понять принцип.
Если нужна глубокая копия
Для данных, которые поддерживает structuredClone:
const copy = structuredClone(user);Похожую функцию cloneDeep предлагает библиотека Lodash.
А если нужно сделать независимым только конкретный вложенный объект, для него пишут отдельный литерал:
const copy = { ...user, settings: { ...user.settings, },};// теперь вы знаете
{ ...user }создаёт новый внешний объект, но значения его свойств копируются как есть. Если значением является ссылка на другой объект, эта ссылка остаётся общей- Поэтому поверхностная копия может быть независима сверху, но иметь общие вложенные объекты:
user !== copy, ноuser.settings === copy. settings - Если независимым нужен конкретный вложенный объект, его тоже можно скопировать через spread:
settings: { ...user.settings } - Копию, в которой вся вложенная структура независима от оригинала, называют глубокой
- Для глубокой копии многих обычных данных можно использовать
structuredClone(user)
Что появится в консоли?
const player = {name: 'Ира',stats: {level: 3,},};const copy = { ...player };copy.name = 'Макс';copy.stats.level = 10;console.log(player.name);console.log(player.stats.level);
У объекта point нет вложенных объектов. Верно ли, что a и b одинаково независимы от point – присваивание любому их свойству не меняет point?
const point = {x: 1,y: 2,};const a = { ...point };const b = structuredClone(point);
Что появится в консоли?
const note = {title: 'Покупки',style: {color: 'green',},};const copy = {style: { ...note.style },...note,};copy.style.color = 'red';console.log(note.style.color);console.log(copy.style === note.style);
Сколько новых объектов создаёт последняя строка?
const order = {id: 7,customer: {name: 'Ира',address: {city: 'Казань',},},paid: true,};const copy = structuredClone(order);
После какой записи строка copy. не изменит user.?
const user = {name: 'Ира',settings: {theme: 'dark',},};