Багато вбудованих функцій 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…)