$ sudo teach IT
РАЗДЕЛ 6 · УРОК 3

Rest-параметры и spread при вызове

Три точки ... — самый многозначный оператор в JavaScript. В зависимости от того, где он стоит, он делает прямо противоположные вещи: rest собирает аргументы в массив, а spread разворачивает массив в аргументы.

⏱ ~20 минут 🎓 Для новичков 🟡 JavaScript ES6

🤔 Проблема: как принять любое количество аргументов?

Представьте: вам нужна функция, которая складывает числа. Но сколько чисел — неизвестно заранее. Иногда два, иногда пять, иногда десять.

// Так не получится — не знаем сколько параметров нужно
function sum(a, b) { return a + b; }          // только 2
function sum(a, b, c) { return a + b + c; }   // только 3
function sum(a, b, c, d) { ... }              // только 4

До ES6 разработчики использовали специальный объект arguments, который автоматически доступен внутри любой функции. Но это было неудобно — arguments не настоящий массив, и с ним нельзя использовать методы массивов.

// Старый способ (до ES6) — работает, но неудобно
function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}
// arguments — псевдомассив, нельзя использовать forEach, map и т.д.

ES6 решил эту проблему элегантно — через rest-параметры.

📦 Rest-параметры: собираем аргументы в массив

Rest-параметр обозначается тремя точками перед именем параметра: ...args. Он «собирает» все оставшиеся аргументы в настоящий массив. Слово «rest» по-английски — «остаток», «остальное».

function sum(...numbers) {
  // numbers — это настоящий массив!
  console.log(numbers); // например: [1, 2, 3, 4, 5]

  let total = 0;
  for (let num of numbers) {
    total += num;
  }
  return total;
}

console.log(sum(1, 2));           // 3
console.log(sum(1, 2, 3));        // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum());               // 0 (пустой массив)

Раз numbers — это настоящий массив, можно использовать любые методы массивов:

function sum(...numbers) {
  // reduce — метод массива, суммирует все элементы
  return numbers.reduce((acc, num) => acc + num, 0);
}

// Или через forEach:
function printAll(...items) {
  items.forEach((item, index) => {
    console.log((index + 1) + ". " + item);
  });
}

printAll("яблоко", "банан", "вишня");
// 1. яблоко
// 2. банан
// 3. вишня

✅ Главное отличие от arguments: rest-параметр — настоящий массив, у него есть все методы: forEach, map, filter, reduce и т.д. Объект arguments — псевдомассив без этих методов.

🔀 Rest вместе с обычными параметрами

Rest-параметр можно комбинировать с обычными параметрами. Главное правило: rest-параметр должен быть последним в списке. Логика та же, что и с дефолтами — он собирает «остальное», что не попало в именованные параметры.

// Первые два аргумента — именованные параметры,
// всё остальное — в массиве tags
function createPost(title, author, ...tags) {
  console.log("Заголовок:", title);
  console.log("Автор:", author);
  console.log("Теги:", tags);
}

createPost("Гайд по JS", "Иван", "javascript", "обучение", "начинающим");
// Заголовок: Гайд по JS
// Автор: Иван
// Теги: ["javascript", "обучение", "начинающим"]

createPost("Привет мир", "Мария");
// Заголовок: Привет мир
// Автор: Мария
// Теги: []  — пустой массив, если тегов нет
// ❌ ОШИБКА: rest должен быть последним
function wrong(first, ...rest, last) {
  // SyntaxError: Rest parameter must be last formal parameter
}

❌ Помни: rest-параметр — всегда последний. JavaScript не знает, где заканчиваются аргументы для rest и начинаются для last — они поступают по порядку, и «граница» может быть только в конце.

📤 Spread при вызове: разворачиваем массив в аргументы

Те же три точки ... при вызове функции работают наоборот: они разворачивают массив или другую итерируемую структуру в отдельные аргументы. Это называется spread-оператор.

function sum(a, b, c) {
  return a + b + c;
}

let numbers = [1, 2, 3];

// Без spread — пришлось бы обращаться по индексам
sum(numbers[0], numbers[1], numbers[2]); // 6 — некрасиво

// Со spread — элегантно!
sum(...numbers); // 6 — числа "высыпаются" из массива как аргументы

Самый частый пример — функции Math.min и Math.max. Они принимают отдельные числа, но не массив. Spread решает эту проблему:

let temperatures = [23, 17, 31, 14, 28, 19];

// Math.max НЕ принимает массив:
Math.max(temperatures);    // NaN — не работает!

// Но принимает аргументы через spread:
Math.max(...temperatures); // 31 — правильно!
Math.min(...temperatures); // 14 — правильно!

🔑 Запомните мнемонику:
Rest (...args в параметрах) — собирает в массив
Spread (...arr при вызове) — разворачивает из массива
Три точки — одно и то же, контекст (место) определяет смысл.

🛠️ Больше примеров со spread

Spread используется не только при вызове функций — он очень универсален. Но его применение при вызове — самое важное для понимания темы этого урока.

// Комбинирование массивов через spread:
let fruits = ["яблоко", "банан"];
let vegetables = ["морковь", "лук"];
let food = [...fruits, ...vegetables];
console.log(food); // ["яблоко", "банан", "морковь", "лук"]

// Копирование массива:
let original = [1, 2, 3];
let copy = [...original]; // полная копия, а не ссылка!
copy.push(4);
console.log(original); // [1, 2, 3] — не изменился
console.log(copy);     // [1, 2, 3, 4]

// Разворачивание строки в символы:
let letters = [..."привет"];
console.log(letters); // ["п", "р", "и", "в", "е", "т"]
// Практический пример: функция принимает числа, а у нас массив
function calculateAverage(...numbers) {
  if (numbers.length === 0) return 0;
  let sum = numbers.reduce((acc, n) => acc + n, 0);
  return sum / numbers.length;
}

// Вызов с отдельными числами:
calculateAverage(4, 8, 6, 2);        // 5

// Есть массив — разворачиваем через spread:
let grades = [4, 8, 6, 2, 10, 7, 5];
calculateAverage(...grades);          // 6

⚖️ Rest vs Spread: финальное сравнение

Rest (...args)
  • Стоит в параметрах при объявлении
  • Собирает аргументы в массив
  • Всегда последний параметр
  • Только один раз в параметрах
  • Создаёт настоящий массив
Spread (...arr)
  • Стоит при вызове функции
  • Разворачивает массив в аргументы
  • Может быть в любом месте
  • Можно использовать несколько раз
  • Работает с любым итерируемым объектом

✅ Итоги урока

  • До ES6 для переменного числа аргументов использовали arguments — псевдомассив
  • Rest-параметр (...args в объявлении) — собирает аргументы в настоящий массив
  • Rest всегда должен быть последним параметром
  • Rest можно комбинировать с обычными параметрами — они берут первые аргументы
  • Spread-оператор (...arr при вызове) — разворачивает массив в отдельные аргументы
  • Три точки ... — один синтаксис, два смысла: место определяет поведение
  • Классический пример spread: Math.max(...arr) для нахождения максимума в массиве

В следующем уроке изучим стрелочные функции — компактный синтаксис ES6, который изменил то, как пишется современный JavaScript. 🚀

Тест: Rest-параметры и spread

6 вопросов

Сумма всех чисел через rest-параметр

Напишите функцию sumAll, которая принимает любое количество чисел через rest-параметр (не массив!) и возвращает их сумму. Если аргументов нет, возвращает 0.

Поиск максимума через spread

Напишите функцию findMax, которая принимает массив чисел и возвращает максимальное число, используя Math.max и spread-оператор. Если массив пустой, верните undefined.