Один синтаксис — две задачи
Три точки ... встречаются в современном JavaScript-коде повсюду, и в зависимости от места они делают две противоположные вещи. Когда ты увидишь закономерность, каждое использование оператора ... в JavaScript будет ложиться в одну из двух корзин:
- Rest:
...имяна принимающей стороне собирает несколько значений в один массив или объект. - Spread:
...значениена отдающей стороне раскладывает массив или объект на отдельные элементы.
Вот и вся ментальная модель. Дальше на этой странице — просто примеры каждого случая и паттерны, к которым ты будешь возвращаться снова и снова.
Rest-параметры: собираем аргументы функции
Rest-параметр в описании функции собирает любое количество аргументов в настоящий массив:
nums — это обычный массив. К нему можно применять .map, .filter, смотреть .length, передавать в другие функции — словом, всё, что доступно массивам.
Rest-параметры можно комбинировать с обычными, но rest-параметр обязательно должен идти последним:
label забирает первый аргумент, а всё остальное попадает в items. Кстати, rest-параметр можно поставить только в конце — иначе получите синтаксическую ошибку.
Rest-параметры против старого объекта arguments
В старом JavaScript-коде внутри обычных функций использовалась «магическая» переменная arguments. С виду массив, но на деле нет — поэтому методы массивов к ней напрямую не применить. Rest-параметры решают эту проблему красиво и без костылей:
У стрелочных функций вообще нет объекта arguments, так что rest-параметры — единственный способ принять переменное количество аргументов. В новом коде лучше сразу использовать ...args.
Spread-оператор при вызове функции
Spread работает в обратную сторону: берёт массив и распаковывает его в отдельные аргументы прямо в месте вызова.
Math.max принимает отдельные числа, а не массив. До появления spread приходилось писать Math.max.apply(null, nums). Теперь достаточно поставить ... — и готово.
Обратите внимание: один и тот же ... работает как rest в определении функции и как spread в её вызове — роль определяется позицией.
Spread оператор в литералах массивов
Spread внутри литерала массива позволяет копировать массивы или объединять их:
[...a] создаёт новый массив с теми же элементами — удобно, когда нужно отсортировать или изменить данные, не трогая оригинал:
scores остался нетронутым, потому что .sort отработал на копии. Привычка вроде мелкая, но когда пишешь код без неожиданных побочных эффектов — окупается сполна.
Spread оператор для объектов
Spread прекрасно работает и с обычными объектами — он сливает их свойства в новый объект:
Побеждает тот ключ, что идёт позже. updates.age перекрывает user.age, а city просто подхватывается заодно. Итоговый объект зависит от порядка спредов — держите это в голове, когда накладываете значения по умолчанию и переопределения:
Сначала — значения по умолчанию, потом — пользовательские. Пользователь «побеждает» по fontSize, а theme наследует.
Подводный камень: поверхностное копирование
Spread копирует только один уровень вложенности. Вложенные объекты и массивы по-прежнему общие у оригинала и копии:
Оба массива показывают новый тег, потому что copy.tags и original.tags — это один и тот же массив. Spread не клонировал вложенный список, а лишь скопировал ссылку на него.
Если нужна настоящая глубокая копия обычных данных, используй structuredClone:
Теперь массивы живут сами по себе. structuredClone уже встроен в современные браузеры и Node, умеет работать с вложенными структурами и выручает всякий раз, когда поверхностного копирования недостаточно.
Rest в деструктуризации
Rest отлично работает и в деструктуризации — он собирает всё, что осталось, будь то элементы массива или свойства объекта:
Выдернуть пару полей, а всё остальное собрать в один объект — типичный приём, когда пробрасываешь пропсы дальше, вычищаешь чувствительные данные или собираешь пропатченную версию объекта:
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(...)).