Skip to main content

Оператори розповсюдження та залишку в JavaScript: відмінності та приклади

Spread (...) та rest (...) мають однаковий синтаксис, але роблять протилежні речі. Spread розпаковує ітерабельне значення в окремі елементи; rest збирає окремі елементи в один масив.

Теорія

TL;DR

  • Spread - це як висипати вміст сумки на стіл: все виходить окремо
  • Rest - це як скласти залишки назад у сумку: все збирається разом
  • Головна різниця: spread стоїть праворуч (або всередині виклику функції), rest стоїть ліворуч (у параметрах або деструктуризації)
  • Копіюєш, об'єднуєш або передаєш дані? Використовуй spread. Захоплюєш зайві аргументи або залишкові пропси? Використовуй rest
  • Rest завжди повертає масив, навіть якщо зайвих елементів немає (повертає [])

Швидкий приклад

javascript
// Spread: розпаковує масив в окремі елементи const nums = [1, 2, 3]; const more = [...nums, 4, 5]; // [1, 2, 3, 4, 5] // Spread у виклику функції function add(a, b, c) { return a + b + c; } add(...nums); // 6 // Rest: збирає залишкові аргументи в масив function sum(first, ...rest) { return first + rest.reduce((a, b) => a + b, 0); } sum(1, 2, 3, 4); // 10, rest = [2, 3, 4]

Контекст визначає, який оператор ти отримуєш. Ті самі три крапки, різні ролі.

Головна відмінність

Spread з'являється праворуч від операторів присвоєння або всередині викликів функцій. Він розбиває ітерабельне значення на частини і повертає окремі елементи. Rest з'являється ліворуч у параметрах функцій або у шаблонах деструктуризації (destructuring). Він збирає все, що залишилось, і загортає в масив. Один розширює, інший стискає.

Коли використовувати

  • Скопіювати масив без мутації: [...arr]
  • Об'єднати об'єкти: { ...obj1, ...obj2 }
  • Передати масив як окремі аргументи у функцію: func(...arr)
  • Захопити змінну кількість аргументів функції: function fn(a, ...rest) {}
  • Деструктурувати з залишковими пропсами: const { x, ...rest } = obj

Таблиця порівняння

ХарактеристикаSpread (...)Rest (...)
ПозиціяПраворуч (присвоєння, виклики)Ліворуч (параметри, деструктуризація)
ЕфектРозгортає в окремі елементиЗбирає залишки в масив
У функціяхfunc(...arr) передає окремі аргументиfunc(a, ...rest) збирає зайві аргументи
Масиви[...arr1, ...arr2] об'єднуєconst [first, ...rest] = arr розбиває
Об'єкти (ES2018+){ ...obj1, ...obj2 } об'єднуєconst { a, ...rest } = obj витягує решту
Коли використовуватиКопіювання, об'єднання, передачаЗмінна кількість аргументів, залишкові пропси

Як це працює

JavaScript-рушії обробляють ... як синтаксичний цукор: spread ітерує джерело через Symbol.iterator (для масивів) або Object.keys (для об'єктів), повертаючи значення по одному. Rest сканує список параметрів під час виклику і пакує хвостові аргументи в новий масив. Обидва створюють поверхневі копії, тому вкладені об'єкти спільно використовують посилання з оригіналом.

У React-проєктах spread зустрічається постійно. Шаблон { ...state, updatedField: newValue } - стандартний спосіб зберігати незмінність у редюсерах без зайвих бібліотек.

Типові помилки

Передача масиву у rest-функцію без spread:

javascript
function add(...nums) { return nums.reduce((a, b) => a + b); } add([1, 2, 3]); // [1,2,3] стає першим аргументом, результат NaN add(...[1, 2, 3]); // правильно: 6

Припущення, що spread робить глибоку копію:

javascript
const a = { nested: { x: 1 } }; const b = { ...a }; a.nested.x = 99; console.log(b.nested.x); // 99 - вкладений об'єкт спільний! // Виправлення: розповсюджуй вкладений об'єкт теж const c = { ...a, nested: { ...a.nested } };

Розповсюдження не-ітерабельного значення:

javascript
[...42]; // TypeError: 42 is not iterable [...'abc']; // працює: ['a', 'b', 'c'] - рядки є ітерабельними [...new Set([1, 2, 2])]; // працює: [1, 2] - Set є ітерабельним

Rest має бути останнім параметром:

javascript
function fn(a, ...rest, b) {} // SyntaxError function fn(a, ...rest) {} // правильно

Реальне використання

  • React: <Button {...buttonProps} /> передає пропси в JSX
  • Redux редюсери: { ...state, count: state.count + 1 } для незмінних оновлень
  • Express middleware: { ...req.query, filter: 'active' } додає параметри запиту
  • Видалення дублікатів через Set: [...new Set([1, 2, 2, 3])] повертає [1, 2, 3]

Питання для практики

Q: Що виведе const a = [1]; const b = [...a]; a.push(2); console.log(b);?
A: [1]. Spread створює новий масив, тому b не залежить від a.

Q: Що використовували до object spread (ES2018)?
A: Object.assign({}, obj1, obj2). Babel досі транспілює object spread до Object.assign для старих цільових платформ, тому результат однаковий.

Q: Що повертає rest, якщо зайвих аргументів немає?
A: Порожній масив []. Це зручніше за старий об'єкт arguments, який був схожий на масив, але масивом не був.

Q: Чи можна розповсюдити Set або Map?
A: Так, обидва є ітерабельними. [...new Set([1, 2, 2])] дає [1, 2]. Зручно для швидкого видалення дублікатів.

Q: Чому rest-параметри кращі за об'єкт arguments?
A: arguments схожий на масив, але не є ним, тому .map() і .reduce() не працюють з ним напряму. Rest повертає справжній масив. До того ж стрілкові функції взагалі не мають власного arguments.

Приклади

Об'єднання налаштувань користувача

javascript
const defaults = { theme: 'light', fontSize: 14, showSidebar: true }; const userPrefs = { fontSize: 18, language: 'en' }; // Пізніші ключі перекривають попередні const settings = { ...defaults, ...userPrefs }; // { theme: 'light', fontSize: 18, showSidebar: true, language: 'en' }

Порядок має значення. ...userPrefs стоїть другим, тому його fontSize перезаписує значення за замовчуванням. Цей шаблон стандартний для об'єднання конфігів та тем.

Логер зі змінною кількістю аргументів

javascript
function log(level, ...messages) { const prefix = `[${level.toUpperCase()}]`; console.log(prefix, ...messages); // знову розповсюджуємо messages } log('info', 'User logged in', 'userId:', 42); // [INFO] User logged in userId: 42

Rest збирає messages у масив. Потім spread передає їх назад як окремі аргументи в console.log. Rest збирає, spread розповсюджує.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?