Багато вбудованих функцій 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
.
Коментарі
<code>
, для кількох рядків – обгорніть їх тегом<pre>
, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)