W ostatnim już artykule z serii dotyczącej nowości w ECMAScript 6 przyjrzymy się bliżej pojęciom Rest operator
oraz Spread properties
i nowym możliwościom, które dzięki nim zyskujemy.
Rest operator
Zacznijmy, jak lubię, od przykładu i powoli wyjaśnimy sobie, co się w nim dzieje:
function f(a, b, ...x) {
return { a, b, x }
}
const result = f('to będzie a', 'to będzie b', 'zas', 'pozostałe', 'wpadną', 'do', 'x');
W pierwszej linijce, jako prefiks ostatniego argumentu w deklaracji funkcji zastosowaliśmy trzy kropki ...
, a więc tak zwany "Rest operator". Zbiera on do tablicy o podanej nazwie wszystkie pozostałe przekazane do funkcji argumenty, które nie zostały wymienione przed nim. W tym przypadku wymienione zostały jedynie a
i b
, a więc wszystkie pozostałe parametry, które do funkcji przekażemy zebrane zostaną w tablicę x
. Innymi słowy result
będzie miał taką zawartość:
{
'a': 'to będzie a',
'b': 'to będzie b',
'x': [
'zaś',
'pozostałe',
'wpadną',
'do',
'x'
]
}
No fajnie, możemy zapomnieć o obiekcie arguments
i jego ograniczeniach, ale do czego można to praktycznie wykorzystać? Pytanie jak najbardziej zasadne, bo zastosowanie nie jest kompletnie oczywiste, a zazwyczaj wiąże się z nieco bardziej zaawansowanymi tematami. Osobiście pierwszy raz skorzystałem z "operatora reszty" (tak to się tłumaczy?) przy tak zwanej aplikacji częściowej. Wkrótce napiszę cały artykuł poświęcony temu tematowi, a na razie niech wystarczy ten prosty przykład:
const greet = (greeting, name) => `${greeting} ${name}`;
Na początek prosta funkcja witająca osobę zadanym powitaniem i imieniem. Póki co nic trudnego. Ale załóżmy, że wykorzystujemy ją na tyle często jako pierwszy argument podając "Hi", że postanowiliśmy uczynić z tego przypadku osobną funkcję, gdzie podajemy tylko imię, a powitanie przez "Hi" mamy już z głowy.
const sayHiTo = (name, ...args) => greet('Hi', name, ...args);
sayHiTo("Józek"); // Hi Józek
No dobra, ale po co tutaj ten rest
? Przecież nawet nigdzie nie wykorzystujemy zmiennej args
. To prawda, nie wykorzystujemy... ale moglibyśmy :-). Tak naprawdę chodzi o to, że nasza funkcja sayHiTo
jest teraz uniwersalna i jeśli zmienimy liczbę parametrów przyjmowanych przez funkcję greet
, to automatycznie będą one obsługiwane także przez funkcję sayHiTo
.
Przykład? Zmieniamy funkcję greet
tak, by przyjmowała jeszcze jeden argument:
const greet = (greeting, name, bonus) => `${greeting} ${name}, ${bonus}`;
I teraz bez zmiany czegokolwiek w funkcji sayHiTo
, zmieniamy jej wywołanie na:
sayHiTo("Józek", "ty byku!");
I otrzymujemy bardziej swojskie "Hi Józek, ty byku!"
Jeśli ten przykład wydaje Ci się nieco wydumany, albo nie rozumiesz, po co tworzyć tego typu funkcje używające już istniejących tylko z pre-uzupełnionym parametrem, proszę uzbrój się w cierpliwość - już wkrótce opublikuję artykuł o programowaniu funkcyjnym w JavaScript, gdzie postaram się lepiej to wyjaśnić. A jeśli nie chcesz czekać, zapraszam do poczytania tutaj (po angielsku).
Spread properties
Spread również zapisywany jest w postaci trzech kropek ...
, lecz nie jako przedrostek do ostatniego argumentu w deklaracji funkcji, a jako przedrostek do jakiejkolwiek tablicy. Jego zadaniem jest "rozbić" tablicę na listę jej elementów. Brzmi to banalnie ale ma to olbrzymią liczbę zastosowań, z których kilka przedstawiam poniżej.
Łączenie tablic
const arr1 = ['trzy', 'cztery'];
const arr2 = ['jeden', 'dwa', ...arr1, 'pięć'];
console.log(arr2); // ['jeden', 'dwa', 'trzy', 'cztery', 'pięć']
Kopiowanie tablic
const arr1 = ['jeden', 'dwa'];
const arr2 = [...arr1];
console.log(arr2); // ['jeden', 'dwa']
Tutaj słowo wyjaśnienia. Ktoś mógłby spytać, dlaczego nie napisać po prostu const arr2 = arr1
. Ano dlatego, że w ten sposób do arr2
przypisujemy jedynie referencję, a więc jakiekolwiek zmiany w arr1
będą także widoczne w arr2
, gdyż będą one po prostu wskazywać na to samo miejsce w pamięci.
Trzeba jednak pamiętać, że taka kopia utworzona przez spread jest jedynie jedno-poziomowa. To znaczy, że jeśli w w arr1
znajdują się jakiekolwiek obiekty, czy tablice, zostaną one skopiowane przez referencje, a nie przez wartość. Aby zrobić klonowanie tablicy upewniając się, że kopiowane są wartości, nie zaś referencje niezależnie od ilości poziomów zagnieżdżenia, należałoby zastosować rekurencję, bądź jedną z dostępnych bibliotek, które takie funkcje już posiadają, jak na przykład lodash.
Usuwanie duplikatów z tablic
const arr = [7, 3, 1, 3, 3, 7];
const noDupes = [...new Set(arr)];
console.log(noDupes); // [ 7, 3, 1 ]
Wykorzystujemy tutaj właściwość nowej struktury danych udostępnionej w ES6, zwanej Set
. Jest to struktura przechowująca elementy, które nie mogą się powtarzać. Set
możemy utworzyć poprzez podanie mu jako argument tablicy. Następnie taki Set
za pomocą spread zamieniamy na listę elementów, które umieszczamy w tablicy. Wszystko w jednej linijce. Czyż to nie piękne? :-)
Zamiana NodeList na tablicę
[...document.querySelectorAll('div')]
To zastosowanie bywa szczególnie przydatne, gdy chcemy wykonać w pętli operacje na wszystkich elementach wybranych przez querySelectorAll
, gdyż funkcja ta nie zwraca tablicy, a właśnie obiekt typu NodeList
, na którym zwykły forEach
nie zadziała (przynajmniej nie we wszystkich przeglądarkach).