Spread-оператор для раскрытия объекта в другой
Синтаксис spread для объектов — три точки перед объектом — копирует его собственные перечисляемые свойства в окружающий объектный литерал. Это самый короткий путь к клонированию, объединению и созданию изменённых копий объекта, не трогая оригинал.
copy содержит те же ключи и значения, что и user, но это уже другой объект. Изменения в одном не затронут другой — по крайней мере, на верхнем уровне. К этой оговорке мы ещё вернёмся.
Как это понимать: запись { ...obj } говорит «вылей свойства obj в этот новый объектный литерал». Всё, что ты допишешь рядом со spread-ом, тоже попадёт в результат.
Клонирование объекта и переопределение свойств в одну строку
Самый ходовой приём — распаковать объект через spread и тут же добавить или переопределить пару свойств. Побеждает тот ключ, что идёт позже, поэтому сначала spread, а переопределения — после:
user остаётся нетронутым. updated — это новый объект, в котором заменено поле role. Этот паттерн иммутабельного обновления встречается повсюду в современном JavaScript: сеттеры состояния в React, редьюсеры в Redux, да и вообще любой код, где не хочется мутировать объект на месте.
Поменяй порядок местами — получишь противоположное поведение:
Здесь role: "guest" идёт первым, поэтому user.role его перезаписывает. Удобно, когда нужны значения по умолчанию, которые потом могут быть переопределены разворачиваемым объектом.
Объединение объектов в JavaScript
Чтобы объединить два (или больше) объекта, разверните их через spread в новый литерал. Свойства из объектов, идущих позже, перезаписывают одноимённые свойства из предыдущих:
theme и fontSize приходят из userPrefs, а debug пробрасывается из defaults. Три объекта? Четыре? Правило то же — читаем слева направо, побеждает последняя запись.
Это современная замена Object.assign({}, defaults, userPrefs). Результат одинаковый, но вариант со spread читается легче и не провоцирует классический баг — написать Object.assign(defaults, userPrefs) и случайно мутировать сам defaults.
Spread делает поверхностную копию (shallow copy)
Вот тут многие и спотыкаются. Spread копирует только свойства верхнего уровня. Если значение свойства — это объект или массив, копируется ссылка, а не само содержимое.
Изменив copy.address.city, мы заодно поменяли и user.address.city — потому что оба объекта ссылаются на один и тот же вложенный address. Spread создал новую обёртку только на верхнем уровне.
Если нужно изменить что-то во вложенной структуре, разворачивай через spread каждый уровень, который хочешь поменять:
Для по-настоящему глубокого клонирования произвольных данных используйте structuredClone(obj). Эта функция корректно работает с вложенными объектами, массивами, датами, Map и Set — и она встроена во все современные рантаймы.
Spread vs rest в JavaScript
Синтаксис у них одинаковый — три точки, — но работают они прямо противоположно. Spread разворачивает, rest собирает.
Простое правило: если ... стоит до знака = (деструктуризация) — это rest. Если же многоточие внутри литерала объекта или массива после = — это spread.
Как удалить свойство без мутации
Связка rest-деструктуризации и spread-оператора даёт элегантный способ получить копию объекта без одного из ключей — без delete и без мутаций исходника:
tempToken вытаскивается в отдельную переменную (которую вы игнорируете), а всё остальное попадает в safe. Исходный user остаётся нетронутым.
Что spread оператор не копирует
Есть пара тонкостей, о которых стоит знать:
- Неперечисляемые свойства не копируются. Большинство свойств, которые вы создаёте, по умолчанию перечисляемые, но свойства, заданные через
Object.defineProperty, а также некоторые встроенные — нет. - Прототип не копируется.
{ ...instance }вернёт вам обычный объект, а не экземпляр исходного класса. Методы, объявленные на прототипе класса, в копии не окажутся. - Геттеры вычисляются. При spread объекта с геттером геттер вызывается один раз, а возвращённое значение сохраняется в новом объекте как обычное свойство.
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. Что перед вами — подскажет контекст.