Menu
Русский

Rest и Spread в JavaScript: оператор ... простыми словами

Разбираемся, как работает оператор ... в JavaScript: rest собирает аргументы функции, spread разворачивает массивы и объекты. Показываю, когда что применять.

Один синтаксис — две задачи

Три точки ... встречаются в современном JavaScript-коде повсюду, и в зависимости от места они делают две противоположные вещи. Когда ты увидишь закономерность, каждое использование оператора ... в JavaScript будет ложиться в одну из двух корзин:

  • Rest: ...имя на принимающей стороне собирает несколько значений в один массив или объект.
  • Spread: ...значение на отдающей стороне раскладывает массив или объект на отдельные элементы.

Вот и вся ментальная модель. Дальше на этой странице — просто примеры каждого случая и паттерны, к которым ты будешь возвращаться снова и снова.

Rest-параметры: собираем аргументы функции

Rest-параметр в описании функции собирает любое количество аргументов в настоящий массив:

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

nums — это обычный массив. К нему можно применять .map, .filter, смотреть .length, передавать в другие функции — словом, всё, что доступно массивам.

Rest-параметры можно комбинировать с обычными, но rest-параметр обязательно должен идти последним:

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

label забирает первый аргумент, а всё остальное попадает в items. Кстати, rest-параметр можно поставить только в конце — иначе получите синтаксическую ошибку.

Rest-параметры против старого объекта arguments

В старом JavaScript-коде внутри обычных функций использовалась «магическая» переменная arguments. С виду массив, но на деле нет — поэтому методы массивов к ней напрямую не применить. Rest-параметры решают эту проблему красиво и без костылей:

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

У стрелочных функций вообще нет объекта arguments, так что rest-параметры — единственный способ принять переменное количество аргументов. В новом коде лучше сразу использовать ...args.

Spread-оператор при вызове функции

Spread работает в обратную сторону: берёт массив и распаковывает его в отдельные аргументы прямо в месте вызова.

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

Math.max принимает отдельные числа, а не массив. До появления spread приходилось писать Math.max.apply(null, nums). Теперь достаточно поставить ... — и готово.

Обратите внимание: один и тот же ... работает как rest в определении функции и как spread в её вызове — роль определяется позицией.

Spread оператор в литералах массивов

Spread внутри литерала массива позволяет копировать массивы или объединять их:

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

[...a] создаёт новый массив с теми же элементами — удобно, когда нужно отсортировать или изменить данные, не трогая оригинал:

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

scores остался нетронутым, потому что .sort отработал на копии. Привычка вроде мелкая, но когда пишешь код без неожиданных побочных эффектов — окупается сполна.

Spread оператор для объектов

Spread прекрасно работает и с обычными объектами — он сливает их свойства в новый объект:

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

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

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

Сначала — значения по умолчанию, потом — пользовательские. Пользователь «побеждает» по fontSize, а theme наследует.

Подводный камень: поверхностное копирование

Spread копирует только один уровень вложенности. Вложенные объекты и массивы по-прежнему общие у оригинала и копии:

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

Оба массива показывают новый тег, потому что copy.tags и original.tags — это один и тот же массив. Spread не клонировал вложенный список, а лишь скопировал ссылку на него.

Если нужна настоящая глубокая копия обычных данных, используй structuredClone:

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

Теперь массивы живут сами по себе. structuredClone уже встроен в современные браузеры и Node, умеет работать с вложенными структурами и выручает всякий раз, когда поверхностного копирования недостаточно.

Rest в деструктуризации

Rest отлично работает и в деструктуризации — он собирает всё, что осталось, будь то элементы массива или свойства объекта:

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

Выдернуть пару полей, а всё остальное собрать в один объект — типичный приём, когда пробрасываешь пропсы дальше, вычищаешь чувствительные данные или собираешь пропатченную версию объекта:

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

password извлекается (и игнорируется), а в safe попадает всё остальное. Ни мутаций, ни ручного копирования.

Кратко повторим

  • ...name в списке параметров или в паттерне деструктуризации — это rest: он собирает.
  • ...value при вызове функции, внутри литерала массива или объекта — это spread: он разворачивает.
  • Копирование через spread — поверхностное. Вложенные структуры остаются общими. Для глубокого клонирования используйте structuredClone.
  • Rest-параметры — это настоящие массивы, так что пользуйтесь ими вместо arguments.
  • В объектных литералах более поздний spread перезаписывает более ранний — именно так удобно собирать схему «значения по умолчанию + переопределения».

Дальше: замыкания

Функции в JavaScript не просто принимают аргументы и возвращают результат — они ещё и помнят область видимости, в которой были объявлены. Эта «память» и называется замыканием (closure), и именно на ней держатся колбэки, фабрики функций и большинство паттернов, с которыми мы познакомимся на следующей странице.

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

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

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

Как работают rest-параметры в функции?

Rest-параметр вроде function sum(...nums) собирает все переданные в функцию аргументы в настоящий массив nums. Важное ограничение: он должен быть последним в списке параметров. В отличие от старого объекта arguments, здесь перед вами полноценный массив, поэтому .map, .filter и .reduce работают с ним напрямую — без всяких Array.from.

Spread делает глубокую копию объекта?

Нет. Spread копирует только верхний уровень — это поверхностная (shallow) копия. Запись { ...user } даёт новый объект с теми же ключами верхнего уровня, но вложенные объекты и массивы остаются общими ссылками. Если нужна глубокая копия — используйте structuredClone(value) или, для простых данных, прогоните через JSON.parse(JSON.stringify(...)).

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

НАЧАТЬ