Spread и rest в JavaScript: три точки
Содержание
О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».
Оператор из трёх точек ... — один из самых полезных в ECMAScript 6. Ловушка новичка в том, что он делает два противоположных дела в зависимости от места. Разберёмся раз и навсегда.
Spread против rest: одно правило
SPREAD — разворачивает
В вызове функции или литерале
fn(...arr)— передать элементы[...a, ...b]— собрать массив{ ...obj }— скопировать объект- Берёт одно → раскладывает на много
REST — собирает
В параметрах или деструктуризации
function f(...args)— все аргументыconst [a, ...rest] = arrconst { x, ...rest } = obj- Берёт много → собирает в один массив/объект
Место решает всё. Если ... стоит там, где создаётся что-то (вызов, литерал массива/объекта) — это spread, он разворачивает. Если там, где что-то принимается (параметры функции, левая часть деструктуризации) — это rest, он собирает. Синтаксис один, направление противоположное.
Spread: копирование и слияние
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4] — слить
const copy = [...a]; // [1, 2] — поверхностная копия
const withExtra = [0, ...a, 3]; // [0, 1, 2, 3] — вставить в середину
// объекты
const base = { name: 'Аня', role: 'user' };
const updated = { ...base, role: 'admin' }; // { name: 'Аня', role: 'admin' }
Slice-копия одной строкой и иммутабельное обновление — главные применения. { ...base, role: 'admin' } создаёт новый объект с изменённым полем, не трогая исходный. Так обновляют состояние в React и Redux: не мутируют, а создают новую версию.
При совпадении ключей побеждает правый — поэтому role: 'admin' перекрывает role: 'user' из base. Порядок важен.
Ловушка: копия поверхностная
const user = { name: 'Аня', address: { city: 'Москва' } };
const copy = { ...user };
copy.name = 'Борис'; // ✅ оригинал цел
copy.address.city = 'Казань'; // ❌ address ОБЩИЙ — оригинал изменится!
... копирует только верхний уровень. Вложенные объекты и массивы остаются общими ссылками — это природа ссылочных типов, а не баг spread. Для настоящей независимости — structuredClone(user). Ошибка на этом месте — классика при работе с состоянием.
Spread в вызове функции
const nums = [5, 2, 8, 1];
Math.max(...nums); // 8 — развернуть массив в аргументы
console.log(...nums); // 5 2 8 1
// объединить массивы через push
const target = [1, 2];
target.push(...[3, 4]); // target станет [1, 2, 3, 4]
Math.max принимает отдельные аргументы, а не массив — spread разворачивает nums в Math.max(5, 2, 8, 1). Это заменило старый неуклюжий Math.max.apply(null, nums).
Spread работает с любым итерируемым: массивом, строкой, Set, Map:
[...'abc']; // ['a', 'b', 'c'] — строка по символам
[...new Set([1, 1, 2])]; // [1, 2] — убрать дубли через Set
Трюк [...new Set(arr)] — самый короткий способ убрать повторы из массива.
Rest: собрать аргументы
function sum(...nums) { // все аргументы → настоящий массив
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15 — сколько угодно
Rest в параметрах собирает произвольное число аргументов в массив. В отличие от старого объекта arguments, nums — настоящий массив, к нему сразу применимы reduce, map, filter.
Комбинируется с обычными параметрами — rest всегда последний:
function log(level, ...messages) { // level — первый, остальное в messages
console.log(`[${level}]`, ...messages);
}
log('info', 'старт', 'сервера'); // [info] старт сервера
Rest в деструктуризации
const [first, ...others] = [1, 2, 3, 4];
// first = 1, others = [2, 3, 4]
const { password, ...safe } = { name: 'Аня', password: '123', role: 'user' };
// safe = { name: 'Аня', role: 'user' } — без пароля
const { password, ...safe } — идиома «убрать одно поле». Вынимаем password, всё остальное собираем в safe — удобно, чтобы отдать объект наружу без чувствительного поля. Подробнее про разбор — в статье о деструктуризации.
rest против arguments
// ❌ старый способ
function oldSum() {
const args = Array.prototype.slice.call(arguments); // arguments — не массив!
return args.reduce((a, b) => a + b, 0);
}
// ✅ современный
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
Старый arguments — псевдомассив: у него есть length, но нет методов массива, и в стрелочных функциях его вообще нет. Rest решает обе проблемы: настоящий массив, работает везде. Новый код arguments не использует.
Смежные темы: деструктуризация — где rest тоже живёт; примитивы и ссылки — почему копия поверхностная; методы массива. Полный список — в уроках JavaScript.