Spread и rest: три точки в двух ролях
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Три точки ... в JavaScript делают ровно две противоположные вещи: разворачивают коллекцию на отдельные значения и собирают отдельные значения обратно в массив. Какая роль включится — решает место, где вы их написали.
Многие функции принимают отдельные аргументы, а данные у нас лежат в массиве. Math.max — классический пример: он ждёт числа через запятую и совершенно не понимает, что делать с массивом.
Spread не заменяет весь список аргументов — он подставляется в него как кусок. Рядом можно писать обычные значения, и порядок сохранится: сначала то, что развернулось, потом остальное.
Внутри квадратных или фигурных скобок те же три точки высыпают содержимое в новый литерал. Самое частое применение — копия: получаем новый массив с теми же элементами, а оригинал остаётся нетронутым.
С объектами работает так же, только копируются пары «ключ — значение». Если ключ встретился дважды, побеждает тот, что записан правее — отсюда удобный приём «настройки по умолчанию плюс пользовательские».
Spread копирует только верхний уровень. Вложенные массивы и объекты попадают в копию теми же ссылками, поэтому изменение «в глубине» видно и в оригинале.
Если нужна полностью независимая копия со всеми уровнями, в современных браузерах есть structuredClone(original) — обе глубины копирования разобраны в статье «Копирование объектов: поверхностное и глубокое».
Те же три точки в списке параметров означают обратное действие: «всё, что передали сюда и дальше, сложи в массив». Такой параметр называется rest — остаточный.
Rest-параметр может стоять только последним, но до него разрешены обычные параметры — они разберут первые аргументы, а остальное достанется массиву.
Раньше для этого использовали arguments — псевдомассив, доступный внутри обычной функции. Он похож на массив, но методов массива у него нет, и в стрелочных функциях его вообще не существует.
При разборе массива или объекта rest собирает всё, что не досталось названным переменным. Для массива это новый массив, для объекта — новый объект.
Второй пример — типовой способ «выбросить» поле: мы не удаляем свойство из оригинала, а собираем новый объект без него. И здесь то же ограничение: const [...all, last] = arr — синтаксическая ошибка, ведь остаток не может закончиться раньше конца.
Символ один и тот же, поэтому запоминать нужно не его, а позицию. Значения читают — в вызове и в литерале — значит spread. Значения объявляют — в параметрах и слева от = — значит rest.
| Где стоят три точки | Роль | Что происходит |
|---|---|---|
Вызов: f(...arr) | spread | массив → отдельные аргументы |
Литерал: [...a], { ...o } | spread | содержимое высыпается в новый литерал |
Параметры: function f(...args) | rest | аргументы → один массив |
Слева от =: const [a, ...rest] = | rest | остаток → новый массив или объект |
Три точки — не самостоятельный оператор: строка const x = ...arr не работает, потому что разворачивать не во что.
- Три точки — не отдельный оператор: смысл им придаёт место, где они написаны.
- Spread стоит в вызове функции или в литерале и разворачивает коллекцию:
f(...arr),[...a, ...b],{ ...defaults, ...user }. - Rest стоит в списке параметров или слева от
=и, наоборот, собирает остаток в новый массив или объект; он всегда последний. - Копия через spread поверхностная — вложенные объекты остаются общими с оригиналом.
- При слиянии объектов выигрывает свойство, записанное правее.
1/3Что выведет console.log(Math.max(...[3, 1, 2]))?
2/3const { a, ...rest } = { a: 1, b: 2, c: 3 }; что лежит в rest?
3/3Какая копия получится из const copy = { ...original }?
Среднее из любого числа аргументов
Напишите функцию avg, которая принимает сколько угодно чисел и возвращает их среднее арифметическое. Для вызова без аргументов верните 0, чтобы не получить деление на ноль.
Rest-параметр даёт настоящий массив, поэтому сразу доступны reduce и length. Проверку на пустоту делаем до деления.
Если данные лежат в массиве, функция всё равно подходит — разверните его при вызове: avg(...[2, 4, 6]).
Скидка, не портящая оригинал
Функция withDiscount должна вернуть новый товар с уменьшенной ценой. Исходный объект менять нельзя — это частая причина трудных багов в приложениях.
Копируем объект через spread и тут же перезаписываем нужное свойство: ключ, записанный после развёртывания, побеждает.
Порядок важен: если написать { price: ..., ...item }, старая цена из item затрёт новую.