[JS] 4 sposoby na dostęp do wartości obiektów i tablic. Destructuring w JavaScript
JavaScript jest językiem w którym każdego roku są wprowadzane kolejne ulepszenia do standardu. W tej części serii zajmiemy się tematem destructuring. Dowiesz się, dlaczego jest to niezmiernie przydatna rzecz w trakcie pisania kodu.
Na początek wyjaśnię czym jest destructuring. Jest to sposób na wydobywanie wartości z obiektów i tablic. Przeważnie jest wykorzystywany w miejscach, gdzie przekazywane są dane do dalszego przetworzenia przez nasz kod JavaScript.
Wydobywanie wartości z obiektów
Pisząc kod skryptu na stronę internetową lub kod aplikacji w JavaScript bardzo często operujemy na obiektach. Bywa, że z całego obiektu który został nam przekazany interesują nas tylko poszczególne własności obiektu, tzw. properties. Za pomocą destructuring możemy przypisać wartości wybranych przez nas własności od razu do zmiennej. Dzięki temu, nasz kod zyska na przejrzystości. Spójrz na przykład poniżej:
1 2 3 4 5 6 7 8 9 10 11 | const user = { name: 'Piotr', type: 'node', info: 'something', id: 21 }; const {name, type} = user; console.log('wynik:', name, type); // wynik: Piotr node |
Jak widać powyżej, z obiektu user
łatwo pobraliśmy interesujące nas wartości z wybranych przez nas kluczy: name
i type
.
A jak wygląda sytuacja w przypadku zagnieżdżonych obiektów? Wygląda to bardzo podobnie, z tą różnicą, że musimy znać strukturę obiektów zagnieżdżonych. Spójrz na przykład poniżej:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | const user = { name: 'Piotr', type: 'node', info: 'something', id: 21, relatives: { wife: { name: 'Magdalena', id: 22 } } }; const {name: myName, relatives: {wife: {name: herName}}} = user; console.log('wynik:', myName, herName); // wynik: Piotr Magdalena |
Tutaj zdecydowanie więcej się dzieje. Przy deklaracji zmiennych wykorzystaliśmy aliasy, dzięki którym o wiele łatwiej jest dostać się do wartości kluczy, które nazywają się tak samo i znajdują się na różnych poziomach zagnieżdżenia.
Na przykładzie powyżej, klucz o nazwie name
znajduje się dwóch miejscach w obiekcie. Gdybyśmy się chcieli odwołać do wartości zmiennej name
za pomocą zapisu: console.log(name);
to uzyskalibyśmy wartość Magdalena
.
Pozyskiwanie wartości z tablic
W podobny sposób możemy dobierać się do wartości z tablic. Różnica polega na tym, że zamiast stosować zapis: {name}
, musimy zastosować zapis: [name]
, tak jak to zostało przedstawione na przykładzie poniżej:
1 2 3 4 5 6 | const list = ['a', 'b', 'c']; const [first, second] = list; console.log('wynik:', first, second); // wynik: a b |
Przykład wydaje mi się wystarczająco czytelny. Utworzyliśmy tablicę z wartościami, a następnie przypisaliśmy wartość pierwszego elementu tablicy do zmiennej first
i wartość drugiego elementu tablicy do zmiennej second
.
Rozważmy nieco inny przykład:
1 2 3 4 5 6 | const list = ['a', 'b', 'c', 'd', 'e', 'f']; const [,second,, ...rest] = list; console.log('wynik:', second, rest); // wynik: b ['d', 'e', 'f'] |
W przykładzie powyżej przypisaliśmy wartości tablicy do dwóch zmiennych: second
i rest
. Wartościami będą kolejno: 'a'
oraz tablica z elementami występującymi po trzecim elemencie tablicy. Pierwszy i trzeci element tablicy nie zostaną przypisane do żadnej zmiennej.
Stosowanie wartości domyślnych
Korzystając z możliwości jakie daje nam destructuring tablic i obiektów możemy stosować również wartości domyślne dla wyznaczonych przez nas zmiennych. Wartości domyślne można zapisać w następujący sposób:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const user = { name: 'Piotr', type: 'node', info: 'something', id: 21 }; const list = ['a', 'b', 'c']; const {name, surname = 'Nalepa'} = user; const [,,,fourth = 'd'] = list; console.log('wynik:', name, surname, fourth); // wynik: Piotr Nalepa d |
Na przykładzie powyżej zadeklarowaliśmy zmienne name
, surname
i fourth
. Ostatnie dwie z nich mają zdefiniowane wartości domyślne, gdyby przekazany obiekt bądź tablica nie zawierały wymaganych przez nas informacji. To przydaje się wtedy, gdy istnieje ryzyko, że z jakiegoś powodu wykorzystawane dane będą niespójne i będzie brakowało jakiejś wymaganej przez nas informacji.
Destructuring parametrów funkcji
Wydobywanie wartości z tablic/obiektów bardzo się przydaje w trakcie tworzenia funkcji, która przyjmuje jakieś parametry. Jako przykłady takich funkcji możemy sobie przyjąć:
- Funkcję wykonywaną po kliknięciu w element na stronie,
- Funkcję wykonywaną w wywołaniu
.then()
, gdy korzystamy z Promises.
W pierwszym przypadku, gdy podepniemy event listener pod wybrany przez nas element, to w funkcji która ma być wywołana po kliknięciu otrzymamy parametr zawierający obiekt:
1 2 3 | const button = document.querySelector('button'); button.addEventListener('click', ({target}) => target.style.background = 'red', false); |
Stosując zapis: ({target}) =>
pozbyliśmy się problemu z pisaniem event.target
jakby to miało miejsce gdybyśmy zastosowali zapis: event =>
. Tak na marginesie, zapis () => {}
to tzw. fat arrow functions. O tym będzie jeden z kolejnych wpisów na blogu.
Powyższy przykład jest prosty, ale jestem w stanie sobie wyobrazić, że w Twoim kodzie jako parametry przekazywane do funkcji mogą być przekazywane inne obiekty. Dodatkowo warto wiedzieć, że w przypadku gdy zależy nam na wielu wartościach przekazywanych jako parametry funkcji, to za pomocą zapisu przedstawionego wcześniej możemy ładnie ograniczyć liczbę parametrów przez co zwiększy się czytelność funkcji w momencie jej wykorzystania w kodzie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // zapis i wykorzystanie funkcji bez użycia destructuring function createUser(firstname, lastname, age) { age = age || '21'; return firstname + ' ' + lastname + ': ' + age; } console.log(createUser('Piotr', 'Nalepa', 30)); // Piotr Nalepa: 30 // zapis i wykorzystanie funkcji z użyciem destructuring createUser({firstname, lastname, age = '21'}) => `${firstname} ${lastname}: ${age}` console.log(createUser({ firstname: 'Piotr', lastname: 'Nalepa', location: 'Dąbrowa Górnicza' })); // Piotr Nalepa: 21 |
Stosując destructuring parametrów funkcji ograniczyliśmy liczbę parametrów jakie przyjmuje funkcja. Dzięki temu zastosowaliśmy się do jednej z reguł dotyczącej standardów czystego kodu, czyli im mniej paramtrów przyjmuje funkcja, tym lepiej.
Teraz zobaczmy jak wygląda destructuring parametrów funkcji w przypadku Promises:
1 2 3 4 5 6 | const promises = [WPAPI.player(), WPAPI.team()]; const assignPlayersToTeams = ([players, teams]) => { // assign players to teams }; Promise.all(promises).then(assignPlayersToTeams); |
W powyższym przykładzie kilka rzeczy wymaga wyjaśnienia aby w pełni zrozumieć co się dzieje w kodzie. Po pierwsze, zapis w stylu WPAPI.player()
jest przykładem wykorzystania klienta REST-owego WordPressa, aby pobrać listę zawodników utworzonych w systemie. Wykonuje ono zapytanie asynchroniczne do serwera i zwraca dane. Podobnie jest z WPAPI.team()
, które zwraca listę drużyn.
Drugą rzeczą o której trzeba wiedzieć jest to, że Promise.all()
przekazuje tablicę z pobranymi danymi jako parametr do callbacka przekazanego w funkcji .then()
. W naszym przypadku, callbackiem jest funkcja assignPlayersToTeams
.
Wiedząc o tym, że jako parametr funkcji assignPlayersToTeams
otrzymujemy tablicę z dwoma alementami, to możemy od razu odwołać się do jej zawartości przypisując elementy tablicy do konkretnych zmiennych players
i teams
. Taki zapis znacząco ułatwia pracę nad kodem funkcji, ponieważ nie musimy już wewnątrz ciała funkcji robić zapisu w stylu:
1 2 3 4 5 6 | const assignPlayersToTeams = data => { const players = data[0]; const teams = data[1]; // assign players to teams }; |
Powyższy zapis wygląda mniej ładnie od poprzedniego, nieprawdaż?
Podsumowanie
W poprzedniej części serii, omawiałem czym są let i const i jak z nich korzystać. W tej części opisałem zagadnienie destructuring i w jaki sposób może się okazać przydatne przy codziennej pracy z kodem JavaScript.
Mam nadzieję, że udało mi się wystarczająco wyjaśnić to zagadnienie i bez problemów będziesz w stanie użyć wyżej wypisane sposoby na przypisanie wartości do zmiennych w swoim kodzie. Zapraszam do komentowania!