5 травня 2025 р.

Залишкові параметри та синтаксис поширення

Багато вбудованих функцій JavaScript підтримують довільну кількість аргументів.

Наприклад:

  • Math.max(arg1, arg2, ..., argN) – повертає найбільший з аргументів.
  • Object.assign(dest, src1, ..., srcN) – копіює властивості з src1..N до dest.
  • …і багато інших.

У цьому розділі ми дізнаємось, як зробити те саме у власній функції. А також, як передавати таким функціям масиви як параметри.

Залишкові параметри ... (з англ. “Rest Parameters”)

Функцію можна викликати з будь-якою кількістю аргументів, незалежно від того, як вона оголошена.

Як от тут:

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

alert(sum(1, 2, 3, 4, 5));

Помилки із-за “надмірних” аргументів у цьому випадку не буде. Але, звісно ж, враховані будуть лише перші два, тому результатом у коді вище є 3.

Решту переданих параметрів можна можна зібрати разом за допомогою трьох крапок ... і після них ім’я змінної з масивом, в який вони передадуться. Ці три крапки буквально означають “зібрати решту параметрів у масив”.

Наприклад, щоб зібрати всі аргументи в масив args:

function sumAll(...args) { // args – це ім’я масиву
  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;
}

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6

Ми можемо задати щоб перші два аргументи мали свої персональні змінні, а в масив пішли всі решта.

У цьому прикладі перші два аргументи переходять у змінні, а решта – в масив titles:

function showName(firstName, lastName, ...titles) {
  alert( firstName + ' ' + lastName ); // Юлій Цезар

  // решта параметрів переходять до масиву
  // titles = ["Консул", "Полководець"]
  alert( titles[0] ); // Консул
  alert( titles[1] ); // Полководець
  alert( titles.length ); // 2
}

showName("Юлій", "Цезар", "Консул", "Полководець");
Залишкові параметри повинні бути в кінці

Залишкові параметри збирають усі додаткові аргументи, тому такий код не спрацює і викличе помилку:

function f(arg1, ...rest, arg2) { // arg2 після ...rest ?!
  // error
}

Залишок ...rest завжди повинен бути останнім.

Змінна “arguments”

Існує також спеціальний псевдомасив, об’єкт arguments, який містить усі аргументи з їх індексами.

Наприклад:

function showName() {
  alert(arguments.length);
  alert(arguments[0]);
  alert(arguments[1]);

  // ми можемо ітерувати по ньому, код нижче працюватиме
  // for(let arg of arguments) alert(arg);
}

// показує: 2, Юлій, Цезар
showName("Юлій", "Цезар");

// показує: 1, Ілля, undefined (другого аргументу немає)
showName("Ілля");

В стародавні часи такої можливості як залишкові параметри в JavaScript не існувало. Тому єдиним способом отримати всі аргументи функції було за допомогою arguments. І він все ще працює, ми можемо знайти його в старому коді.

Але недоліком є те, що хоч arguments є одночасно і псевдомасивом, і ітерованим об’єктом, та все ж це об’єкт, а не масив. Він не підтримує методи масиву, тому ми не можемо наприклад викликати arguments.map(...).

Крім того, він завжди містить усі аргументи. Ми не можемо отримати лише частину з них, як це можна робити з допомогою залишкових параметрів.

Тому, коли нам потрібні вбудовані методи масивів, тоді краще використати залишкові параметри.

Стрілкові функції не мають "arguments"

Якщо ми спробуємо звернутись до об’єкта arguments всередині стрілкової функції, то отримаємо його з зовнішньої “звичайної” функції.

Ось приклад:

function f() {
  let showArg = () => alert(arguments[0]);
  showArg();
}

f(1); // 1

Як ми пам’ятаємо, стрілкові функції не мають власних this. Тепер ми знаємо, що в них також немає особливого об’єкту arguments.

Синтаксис розширення [#spread-syntax] (з англ. “Spread Syntax”)

Ми щойно побачили, як отримати масив зі списку параметрів.

Але іноді нам потрібно зробити зворотнє.

Наприклад, є вбудована функція Math.max що повертає найбільше число зі списку:

alert( Math.max(3, 5, 1) ); // 5

Тепер припустімо, що у нас є масив [3, 5, 1]. Як нам викликати Math.max з цим?

Передати “як є” не вийде, бо Math.max очікує список числових аргументів, а не єдиний масив:

let arr = [3, 5, 1];

alert( Math.max(arr) ); // NaN

І, звісно ж, ми не можемо вручну перераховувати елементи в коді Math.max(arr[0], arr[1], arr[2]), тому що ми можемо й не знати, скільки їх існує. Під час виконання нашого сценарію їх може бути багато, а може і не бути. Та й взагалі робити це вручну було б дивно і неефективно.

Нам допоможе синтаксис розширення! Він схожий на параметри залишку, також використовуються ..., але працює все навпаки.

Коли ...arr використовується в дужках підчас виклику функції, він “розширює” ітеровуваний об’єкт arr до списку аргументів.

Для Math.max:

let arr = [3, 5, 1];

alert( Math.max(...arr) ); // 5 (перетворює масив у список аргументів)

Таким чином, ми також можемо передати кілька ітерацій:

let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(...arr1, ...arr2) ); // 8

Ми навіть можемо поєднати синтаксис розширення з нормальними значеннями:

let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25

Також синтаксис розширення можна використовувати для об’єднання масивів:

let arr = [3, 5, 1];
let arr2 = [8, 9, 15];

let merged = [0, ...arr, 2, ...arr2];

alert(merged); // 0,3,5,1,2,8,9,15 (0, тоді arr, тоді 2, тоді arr2)

У наведених вище прикладах для демонстрації синтаксису розширення ми використовували масив, але підходить будь-який ітерований об’єкт.

Наприклад, тут ми використовуємо синтаксис розширення, щоб перетворити рядок у масив символів:

let str = "Hello";

alert( [...str] ); // H,e,l,l,o

Синтаксис розширення під капотом працює з ітерованими об’єктами так само, як це робить for..of.

Отже, для рядка, for..of повертає символи так само як і ...str, врешті-решт рядок перетворюється на "H","e","l","l","o". Список символів передається в ініціалізатор масиву [...str].

Для цього конкретного завдання ми також могли б використовувати Array.from, бо він перетворює ітерований об’єкт (то й же рядок, або щось інше) на масив:

let str = "Hello";

// Array.from перетворює ітерований об'єкт на масив
alert( Array.from(str) ); // H,e,l,l,o

Результат такий самий як при [...str].

Але між Array.from(obj) та [...obj] є тонка різниця:

  • Array.from працює як з псевдомасивами, так і з ітерованими об’єктами.
  • Синтаксис розширення працює тільки з ітерованими об’єктами.

Отже, якщо треба перетворити щось на масив, то Array.from буде більш універсальним.

Створити копію масива/об’єкта

Пам’ятаєте ми раніше говорили про Object.assign() в минулому розділі?

Те ж саме можна зробити і з синтаксисом розширення.

let arr = [1, 2, 3];

let arrCopy = [...arr]; // розширить масив у список параметрів
                        // а потім помістіть результат у новий масив

// чи мають масиви однаковий вміст?
alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true

// чи масиви однакові?
alert(arr === arrCopy); // false (не однакові посилання)

// зміна нашого початкового масиву не змінює копію:
arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3

Зауважте, що те ж саме можна зробити, щоб зробити копію об’єкта:

let obj = { a: 1, b: 2, c: 3 };

let objCopy = { ...obj }; // розширить об'єкт у список параметрів
                          // потім поверне результат у новий об’єкт

// чи однаковий вміст мають об’єкти?
alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true

// чи однакові об’єкти?
alert(obj === objCopy); // false (не однакові посилання)

// зміна нашого початкового об'єкта не змінює копію:
obj.d = 4;
alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}

Цей спосіб копіювання об’єкта набагато коротший, ніж let objCopy = Object.assign({}, obj) чи для масиву let arrCopy = Object.assign([], arr). Тому ми вважаємо за краще використовувати його, коли це можливо.

Підсумки

Коли ми бачимо "..." у коді – це або залишкові параметри, або синтаксис розширення.

Існує простий спосіб відрізнити їх:

  • Коли ... знаходиться в кінці параметрів функції, це “залишкові параметри” і він збирає решту переданих аргументів у масив.
  • Коли ... виникає під час виклику функції чи чогось подібного, це називається “синтаксисом розширення” і розширює масив у список.

Паттерни використання:

  • Залишкові параметри використовуються для створення функцій, які приймають будь-яку кількість аргументів.
  • Синтаксис розширення використовується для передачі масиву у функції, які зазвичай вимагають список із багатьох аргументів.

Разом вони допомагають легко переміщатися між списком та масивом параметрів.

Усі аргументи виклику функції також доступні в “олдскульному” ітерованому об’єкті arguments.

Навчальна карта

Коментарі

прочитайте це, перш ніж коментувати…
  • Якщо у вас є пропозиції, щодо покращення підручника, будь ласка, створіть обговорення на GitHub або одразу створіть запит на злиття зі змінами.
  • Якщо ви не можете зрозуміти щось у статті, спробуйте покращити її, будь ласка.
  • Щоб вставити код, використовуйте тег <code>, для кількох рядків – обгорніть їх тегом <pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)