16 липня 2023 р.

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

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

Наприклад:

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

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

Залишкові параметри ...

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

Як тут:

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, Julius, Caesar
showName("Julius", "Caesar");

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

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

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

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

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

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

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

Ось приклад:

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

f(1); // 1

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

Синтаксис розширення

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

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

Наприклад, є вбудована функція 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, then arr, then 2, then 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…)