Spread и Rest

Spread (...) раскладывает массив/объект на элементы. Rest (...) собирает оставшиеся элементы в массив/объект.

Зачем нужно

Spread упрощает копирование, слияние и передачу данных. Rest позволяет функциям принимать произвольное количество аргументов.

Где используется

  • Копирование и слияние массивов/объектов
  • Передача аргументов в функции
  • Вариативные функции (variadic)
  • Иммутабельные обновления (React state)

Предпосылки

Массивы, Объекты, Деструктуризация

Spread в массивах

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Объединение массивов
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// Копия массива (поверхностная)
const copy = [...arr1]; // [1, 2, 3]

// Добавление элементов
const withStart = [0, ...arr1];     // [0, 1, 2, 3]
const withEnd = [...arr1, 4];       // [1, 2, 3, 4]
const withMiddle = [0, ...arr1, 4]; // [0, 1, 2, 3, 4]

// Из строки в массив
const chars = [..."Привет"]; // ['П', 'р', 'и', 'в', 'е', 'т']

// Из Set в массив (удаление дубликатов)
const unique = [...new Set([1, 2, 2, 3, 3])]; // [1, 2, 3]

// Из NodeList в массив
const divs = [...document.querySelectorAll('div')];

Spread в объектах

const defaults = { theme: 'light', lang: 'ru', debug: false };
const userPrefs = { theme: 'dark', fontSize: 16 };

// Слияние объектов (последний побеждает)
const config = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'ru', debug: false, fontSize: 16 }

// Копия объекта (поверхностная)
const copy = { ...defaults };

// Добавление/перезапись свойств
const updated = { ...defaults, debug: true, version: 2 };

// Иммутабельное обновление (React-стиль)
const state = { count: 0, name: 'test' };
const newState = { ...state, count: state.count + 1 };
// { count: 1, name: 'test' } — state не изменён

Spread в вызовах функций

const numbers = [5, 2, 8, 1, 9];

// Math.max / Math.min
Math.max(...numbers); // 9
Math.min(...numbers); // 1

// Вместо apply
function sum(a, b, c) {
  return a + b + c;
}
const args = [1, 2, 3];
sum(...args); // 6
// До ES6: sum.apply(null, args)

// console.log
console.log(...['a', 'b', 'c']); // a b c

Rest в параметрах функции

// Собирает «остаток» аргументов в массив
function sum(...numbers) {
  return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3);       // 6
sum(1, 2, 3, 4, 5); // 15

// Первый параметр отдельно, остальные — rest
function log(level, ...messages) {
  console.log(`[${level}]`, ...messages);
}
log('ERROR', 'Файл не найден', 'path/to/file');
// [ERROR] Файл не найден path/to/file

// Rest ВСЕГДА последний параметр
// function bad(...rest, last) {} // SyntaxError!

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

// Массивы
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first = 1, second = 2, rest = [3, 4, 5]

// Объекты
const { name, ...otherProps } = { name: 'Алиса', age: 25, city: 'Москва' };
// name = 'Алиса', otherProps = { age: 25, city: 'Москва' }

// Практика: удаление свойства иммутабельно
const user = { id: 1, name: 'Алиса', password: 'secret' };
const { password, ...safeUser } = user;
// safeUser = { id: 1, name: 'Алиса' } — без пароля

Spread vs Rest — как отличить

// SPREAD — в правой части, раскладывает
const arr = [...otherArr]; // раскладывает otherArr
const obj = { ...otherObj }; // раскладывает otherObj
fn(...args); // раскладывает args при вызове

// REST — в левой части, собирает
const [a, ...rest] = arr; // собирает остаток в rest
const { x, ...others } = obj; // собирает остаток в others
function fn(...args) {} // собирает аргументы в args

Важно: поверхностная копия

const original = {
  name: 'Алиса',
  hobbies: ['чтение', 'музыка'],
  address: { city: 'Москва' }
};

const copy = { ...original };
copy.name = 'Боб';                // OK — примитив скопирован
copy.hobbies.push('спорт');      // Мутирует оригинал!
copy.address.city = 'Питер';     // Мутирует оригинал!

console.log(original.hobbies);    // ['чтение', 'музыка', 'спорт']
console.log(original.address.city); // 'Питер'

// Решение: structuredClone для глубокой копии
const deepCopy = structuredClone(original);

Частые ошибки

1. Spread не клонирует глубоко

// См. пример выше — вложенные объекты копируются по ссылке

2. Spread с null/undefined

const a = null;
// [...a]; // TypeError: null is not iterable

// Для объектов — OK
const obj = { ...null }; // {} — пустой объект
const obj2 = { ...undefined }; // {} — тоже OK

3. Порядок при слиянии

// Последний побеждает
const result = { a: 1, ...{ a: 2 } }; // { a: 2 }
const result2 = { ...{ a: 2 }, a: 1 }; // { a: 1 }

Практика

  1. Напиши функцию mergeArrays(...arrays) — объединяет произвольное число массивов
  2. Напиши функцию omit(obj, ...keys()) — возвращает объект без указанных ключей
  3. Реализуй иммутабельное обновление вложенного объекта
  4. Используй spread для вызова функции с массивом аргументов

Связанные темы

Ресурсы


⚡ Источник: Выражаем себя через Rest и Spread · AsForJS

  • 📅 2025-10-31 · YouTube
  • Тезисы:
    • Spread и Rest — это РАЗНЫЕ грамматические сущности, не один оператор. Их объединяет только токен ...
    • Spread в массиве/вызове функции использует Symbol.iterator — работает только с iterables ([...new Set] OK, [...{}] — TypeError)
    • Spread в object literal {...obj} НЕ использует итератор — копирует enumerable own properties (включая Symbol-ключи!)
    • Rest в параметре функции создаёт настоящий Array (в отличие от arguments), который оптимизируется V8
  • Цитата: «Spread не один оператор. Это три разных синтаксиса в трёх контекстах»