Menu
Русский

Spread для объектов в JavaScript: копирование и слияние

Разбираем оператор spread для объектов в JavaScript: как клонировать, объединять, переопределять свойства и не попасться на поверхностном копировании.

Spread-оператор для раскрытия объекта в другой

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

index.js
Output
Click Run to see the output here.

copy содержит те же ключи и значения, что и user, но это уже другой объект. Изменения в одном не затронут другой — по крайней мере, на верхнем уровне. К этой оговорке мы ещё вернёмся.

Как это понимать: запись { ...obj } говорит «вылей свойства obj в этот новый объектный литерал». Всё, что ты допишешь рядом со spread-ом, тоже попадёт в результат.

Клонирование объекта и переопределение свойств в одну строку

Самый ходовой приём — распаковать объект через spread и тут же добавить или переопределить пару свойств. Побеждает тот ключ, что идёт позже, поэтому сначала spread, а переопределения — после:

index.js
Output
Click Run to see the output here.

user остаётся нетронутым. updated — это новый объект, в котором заменено поле role. Этот паттерн иммутабельного обновления встречается повсюду в современном JavaScript: сеттеры состояния в React, редьюсеры в Redux, да и вообще любой код, где не хочется мутировать объект на месте.

Поменяй порядок местами — получишь противоположное поведение:

index.js
Output
Click Run to see the output here.

Здесь role: "guest" идёт первым, поэтому user.role его перезаписывает. Удобно, когда нужны значения по умолчанию, которые потом могут быть переопределены разворачиваемым объектом.

Объединение объектов в JavaScript

Чтобы объединить два (или больше) объекта, разверните их через spread в новый литерал. Свойства из объектов, идущих позже, перезаписывают одноимённые свойства из предыдущих:

index.js
Output
Click Run to see the output here.

theme и fontSize приходят из userPrefs, а debug пробрасывается из defaults. Три объекта? Четыре? Правило то же — читаем слева направо, побеждает последняя запись.

Это современная замена Object.assign({}, defaults, userPrefs). Результат одинаковый, но вариант со spread читается легче и не провоцирует классический баг — написать Object.assign(defaults, userPrefs) и случайно мутировать сам defaults.

Spread делает поверхностную копию (shallow copy)

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

index.js
Output
Click Run to see the output here.

Изменив copy.address.city, мы заодно поменяли и user.address.city — потому что оба объекта ссылаются на один и тот же вложенный address. Spread создал новую обёртку только на верхнем уровне.

Если нужно изменить что-то во вложенной структуре, разворачивай через spread каждый уровень, который хочешь поменять:

index.js
Output
Click Run to see the output here.

Для по-настоящему глубокого клонирования произвольных данных используйте structuredClone(obj). Эта функция корректно работает с вложенными объектами, массивами, датами, Map и Set — и она встроена во все современные рантаймы.

Spread vs rest в JavaScript

Синтаксис у них одинаковый — три точки, — но работают они прямо противоположно. Spread разворачивает, rest собирает.

index.js
Output
Click Run to see the output here.

Простое правило: если ... стоит до знака = (деструктуризация) — это rest. Если же многоточие внутри литерала объекта или массива после = — это spread.

Как удалить свойство без мутации

Связка rest-деструктуризации и spread-оператора даёт элегантный способ получить копию объекта без одного из ключей — без delete и без мутаций исходника:

index.js
Output
Click Run to see the output here.

tempToken вытаскивается в отдельную переменную (которую вы игнорируете), а всё остальное попадает в safe. Исходный user остаётся нетронутым.

Что spread оператор не копирует

Есть пара тонкостей, о которых стоит знать:

  • Неперечисляемые свойства не копируются. Большинство свойств, которые вы создаёте, по умолчанию перечисляемые, но свойства, заданные через Object.defineProperty, а также некоторые встроенные — нет.
  • Прототип не копируется. { ...instance } вернёт вам обычный объект, а не экземпляр исходного класса. Методы, объявленные на прототипе класса, в копии не окажутся.
  • Геттеры вычисляются. При spread объекта с геттером геттер вызывается один раз, а возвращённое значение сохраняется в новом объекте как обычное свойство.
index.js
Output
Click Run to see the output here.

copy содержит x и y, но это обычный объект — метод distance живёт на Point.prototype, куда spread не заглядывает. Если нужно клонировать экземпляр класса, обычно сам класс должен предоставлять метод clone.

Что дальше: методы массивов

Spread — лишь часть инструментария для работы с неизменяемыми данными. Другая крупная часть — методы массивов: map, filter, reduce и компания, которые возвращают новые массивы, а не изменяют исходные. Об этом — на следующей странице.

Часто задаваемые вопросы

Что делает ...obj в JavaScript?

Внутри литерала объекта ...obj копирует все собственные перечисляемые свойства в новый объект. Запись { ...user } создаёт новый объект с теми же ключами и значениями, что и у user. Это стандартный способ склонировать или объединить объекты, не мутируя исходники.

Как объединить два объекта в JavaScript?

Достаточно развернуть оба внутри нового литерала: const merged = { ...a, ...b }. Свойства из b перезапишут совпадающие ключи из a — побеждает тот, кто указан позже. По сути то же самое, что Object.assign({}, a, b), только читается приятнее.

Spread — это глубокое копирование?

Нет, spread делает только поверхностную копию (shallow copy): свойства верхнего уровня копируются, а вложенные объекты и массивы по-прежнему живут по ссылке. Если изменить copy.address.city, изменится и original.address.city. Для полноценной глубокой копии используйте structuredClone(obj).

В чём разница между spread и rest?

Синтаксис один и тот же (...), но задачи противоположные. Spread разворачивает объект или массив на отдельные свойства и элементы: { ...user }. Rest, наоборот, собирает остаток в одну переменную: const { name, ...others } = user. Что перед вами — подскажет контекст.

Учитесь программировать с Coddy

НАЧАТЬ