Skip to content

Latest commit

 

History

History
295 lines (191 loc) · 13.7 KB

File metadata and controls

295 lines (191 loc) · 13.7 KB

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

Багато вбудованих функцій 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("Юлій", "Цезар", "Консул", "Полководець");
Залишкові параметри збирають усі залишені аргументи, тому такий код не спрацює і викличе помилку:

```js
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(...).

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

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

````smart header="Стрíлкові функції не мають \"arguments\"" Якщо ми викличемо `arguments` об'єкта всередині стрíлкової функції, він візьме їх із зовнішньої "звичайної" функції.

Ось приклад:

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

f(1); // 1

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



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

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

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

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

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

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

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

```js run
let arr = [3, 5, 1];

*!*
alert( Math.max(arr) ); // NaN
*/!*
```

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

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

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

Для `Math.max`:

```js run
let arr = [3, 5, 1];

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

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

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

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

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


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

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

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

```js run
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)
```

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

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

```js run
let str = "Hello";

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

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

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

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

```js run
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()` [в минулому розділі](info:object-copy#cloning-and-merging-object-assign)?

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

```js run
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
```

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

```js run
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`.