Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!

🔥 Webinar Q&A na temat IT – pytaj, o co chcesz! już 24.11.2022 (czwartek) o 20:30

Czym jest destrukturyzacja w JavaScript i jak ją stosować

Szczegółowe omówienie dla obiektów, tablic i stringów

Destrukturyzację w JS stosuje się na co dzień – choćby przy komunikacji między komponentami czy pracy z hookami w bibliotece React. W sposób prosty i czytelny pozwala nam ona tworzyć zmienne i przypisywać do nich wartości pochodzące z tablic, obiektów oraz stringów. Poznaj jej działanie!

Spis treści

 Dla kogo jest ten artykuł

Materiał ten przeznaczony jest dla osób, które poznały już stringi, tablice oraz obiekty w JavaScripcie i zaczynają operować na danych zgromadzonych w ich postaci. Jeśli jeszcze nie wiesz, jak np. wyświetlić wartość tablicy spod konkretnego indeksu lub wartość danej właściwości obiektu, to najpierw uzupełnij wiedzę (polecam mój artykuł „Obiekty w JavaScript – czym są i jak z nich korzystać”).

 Czym jest destrukturyzacja w JavaScript

Destrukturyzacja to inaczej przypisanie destrukturyzujące (ang. destructuring assignment) – i to już nam mówi, że chodzi właśnie o przypisanie. Czego do czego? Wartości do zmiennej.

Ważne: destrukturyzacja nie zmienia struktury poddawanych jej obiektów, tablic czy stringów – nie musisz więc się martwić, że przez nią coś w kodzie przypadkowo zmodyfikujesz.

 Destrukturyzacja obiektu, tablicy i stringu

 Obiekt

Załóżmy, że chcę mieć dwie samodzielne zmienne: dog i cat, a do nich przypisane wartości – imiona pupili. Niestety na razie wszystko opakowane jest w obiekt:

const pets = {
  dog: "Woofer",
  cat: "Mimi"
}

Bez destrukturyzacji musiałbym stworzyć zmienne w dwóch osobnych liniach i przypisać im wartości, odwołując się do właściwości obiektu po kropce (lub w nawiasie kwadratowym):

const dog = pets.dog;
const cat = pets.cat;

Dzięki destrukturyzacji możemy utworzyć zmienne z wartościami obiektu łatwiej i szybciej. Ja wybrałem const, ale nic nie stoi na przeszkodzie, by była to zmienna let czy przestarzała var:

const { dog, cat } = pets;

Od teraz zmienna dog przechowuje wartość "Woofer", a zmienna cat – "Mimi"!

Schemat destrukturyzacji obiektu wygląda tak (zwróć uwagę na nawiasy klamrowe):

  • const/let/var – stwórz konkretne zmienne
  • { nazwa, nazwa } – o nazwach właściwości obiektu
  • = – i przypisz do nich wartości
  • objName – tego obiektu.

Destrukturyzacja obiektu

No dobrze, a co jeśli chcę mieć zmienne z wartościami obiektu, ale nie pasują mi nazwy jego właściwości? Co jeśli zmienne chcę nazwać inaczej, np. doggy i kitty? Zapis jest bardzo podobny, dodajemy tylko własną nazwę po dwukropku:

const pets = {
  dog: "Woofer",
  cat: "Mimi"
}
const { dog: doggy, cat: kitty } = pets;

Gotowe. Zmienne nazywają się teraz doggy i kitty, a dog i cat nic nie znaczą – tzn. nie są zmiennymi, pozostają po prostu właściwościami obiektu. Warto o tym pamiętać, bo czasem jest to źródłem błędów – gdy wydaje nam się, że zmienna powinna istnieć, a jednak przy destrukturyzacji nadaliśmy jej inną nazwę.

 Tablica

W destrukturyzacji obiektu znaczenie miały nazwy właściwości obiektu. W destrukturyzacji tablic znaczenie ma kolejność. Sam zapis jest niemal identyczny – inne są tylko nawiasy: kwadratowe.

Załóżmy, że mamy tablicę polskich nazw owoców. Chcemy przypisać je do zmiennych o nazwach angielskich:

const fruit = [ "jabłko", "banan", "cytryna" ];

const [ apple, banana, lemon ] = fruit;

Taki sam efekt uzyskalibyśmy, pisząc:

const apple = fruit[0];
const banana = fruit[1];
const lemon = fruit[2];

Widzisz? Odwołujemy się do kolejnych indeksów. Jeśli pomieszamy kolejność nazw zmiennych przy destrukturyzacji, to otrzymają one nie te wartości, o które nam chodzi:

const fruit = [ "jabłko", "banan", "cytryna" ];

const [ banana, lemon, apple ] = fruit;

Teraz zmienna banana ma wartość "jabłko", lemon – "banan", a apple – "cytryna".

Destrukturyzacja tablicy

 String

Nie sądzę, byś często tego potrzebował, ale warto mieć świadomość, że stringi również podlegają destrukturyzacji. Tak samo jak w tablicy ich znaki mają indeksy, toteż kolejność ma znaczenie.

const [ letterOne, letterTwo, letterThree ] = "pencil"

To samo uzyskalibyśmy pisząc:

const letterOne = "pencil"[0];
const letterTwo = "pencil"[1];
const letterThree = "pencil"[2];

Pierwsza zmienna przechowuje literę "p", druga – "e", a trzecia – "n". Reszta liter – jeśli nie uwzględnimy odpowiedniej liczby zmiennych przy destrukturyzacji – po prostu nie zostanie przypisana do niczego, nie będzie błędu.

A co, jeśli nie znamy liczby elementów (w związku z czym nie stworzymy przy destrukturyzacji adekwatnej liczby zmiennych), lecz poza np. trzema pierwszymi gdzieś mimo wszystko chcemy przechowywać resztę? Z pomocą przychodzi rozproszenie.

 Destrukturyzacja i rozproszenie

Może się zdarzyć, że do zmiennych będziemy chcieli przypisać tylko jedną lub kilka wybranych właściwości, ale jednocześnie zachować gdzieś pozostałą część danych. Możemy to zrobić dzięki rozproszeniu (więcej o nim przeczytasz w artykule Rozproszenie i parametr reszty w JavaScript – jak ich nie mylić.

 Rozproszenie obiektu

Weźmy nasz przykład ze zwierzętami, tylko dodajmy do niego jeszcze dwa elementy:

const pets = {
  dog: "Woofer",
  cat: "Mimi",
  fish: "Gold",
  tortoise: "Vinci"
}

Załóżmy, że zabieramy psa na spacer, a resztę zostawiamy w domu. Chcemy w destrukturyzacji uwzględnić zarówno psa, jak i resztę zwierzaków, lecz bez potrzeby przypisywania każdego pupila do osobnej zmiennej. Zrobimy to w ten sposób:

const { dog, ...otherPets } = pets

Od teraz zmienna dog przechowuje wartość "Woofer", a zmienna otherPets przechowuje obiekt:

{
  cat: "Mimi",
  fish: "Goldy",
  tortoise: "Vinci"
}

Dzięki temu moglibyśmy np. napisać funkcję, która wyświetli pozostałe zwierzęta – bez względu na to, ile ich mamy w obiekcie. Spróbuj to zrobić. Do funkcji przez parametr przekaż obiekt otherPets i wyświetl zdanie:

I have other pets at home. It is a cat Mimi, a fish Goldy, and a tortoise Vinci.

 Rozproszenie tablicy

Jeśli z jakiegoś powodu interesuje nas tylko jeden czy kilka pierwszych elementów tablicy, lecz resztę też chcemy gdzieś przechowywać, to działamy bardzo podobnie jak w przypadku obiektów, zmieniają się tylko nawiasy na kwadratowe:

const fruit = [ "jabłko", "banan", "cytryna", "pomarańcza", "kiwi" ];

const [ firstWord, ...otherWords ] = fruit;

Dzięki temu zmienna firstWord przechowuje wartość "jabłko", a zmienna otherWords – tablicę pozostałych słów:

[ "banan", "cytryna", "pomarańcza", "kiwi" ]

Można tego użyć np. w aplikacji do nauki angielskiego. Spróbuj stworzyć kod, który wyświetli:

Słowo jabłko już poznałeś. Poniżej wpisz jego angielską nazwę.

A teraz naucz się słów: banan, cytryna, pomarańcza i kiwi. Za chwilę sprawdzisz swoją wiedzę!

 Rozproszenie stringu

Dzięki rozproszeniu stringu moglibyśmy stworzyć np. grę, w której podajemy pierwszą literę słowa, a reszta to rozsypanka.

const word = "pencil";

const [ firstLetter, ...otherLetters ] = word;

Dzięki temu zmienna firstLetter przechowuje wartość "p", a zmienna otherLetterstablicę (!) pozostałych liter.

Moglibyśmy stworzyć funkcję, która pomiesza elementy tablicy otherLetters, tak by uzyskać rozsypankę literową. Wynik działania aplikacji mógłby wyglądać tak:

Ułóż wyraz z podanych liter.

n e p l c i

Podpowiedź: pierwsza litera to p.

 Destrukturyzacja elementów zagnieżdżonych

Zanim poznasz ten temat, powinieneś nauczyć się sprawnie poruszać po obiektach i tablicach, czyli rozumieć sposób uzyskiwania wartości z zagnieżdżonych w nich elementów. Jest to kluczowe dla prawidłowego zastosowania ich destrukturyzacji.

 Zagnieżdżony obiekt

Z API pogodowego otrzymaliśmy taki obiekt:

const weather = {
  temperature: "23",
  condition: {
      "text": "Partly cloudy",
      "code": 267
    }
}

Żeby „dostać się” do opisu zachmurzenia musimy zastosować zapis: weather.condition.text (oczywiście można też napisać weather["condition"]["text"] – choć to mniej czytelne). Jeśli to rozumiesz, przejdź dalej.

Gdybyśmy chcieli z pomocą destrukturyzacji przekazać obiekt condition do zmiennej, to zrobilibyśmy to tak:

const { condition } = weather

A jeśli chcemy się dostać głębiej, do właściwości text, to napiszemy:

const { condition: { text } } = weather

Znaczy to: w obiekcie weather znajdź obiekt condition i z jego wnętrza pobierz wartość właściwości text. Przypisz tę wartość do zmiennej text. Gotowe – w ten sposób otrzymaliśmy zmienną text z wartością "Partly cloudy".

Uwaga: w takim momencie słowo condition nic nie znaczy (prócz tego, że nadal jest właściwością obiektu) – nie staje się zmienną! To po prostu ogniwo potrzebne do „dostania się” do zagnieżdżonej wartości.

 Zagnieżdżona tablica

Destrukturyzacja zagnieżdżonej tablicy będzie wyglądać podobnie – trzeba jednak pamiętać, że kolejność ma znaczenie. Nie sądzę jednak, byś często się z nią spotykał.

const numbers = [ 1, 2, [ 101, 102 ] ]

Chcemy przypisać do zmiennej wartość 101. Możemy to zrobić np. tak:

const [ digitOne, digitTwo, [ numberOne ] ] = numbers;

Lecz teoretycznie nie zależy nam na dwóch pierwszych cyfrach, możemy więc je pominąć, wykorzystując zapis z przecinkami (jeden przecinek oznacza jedną pominiętą wartość):

const [ ,, [ numberOne ] ] = numbers;

W obu przypadkach zmienna numberOne będzie przechowywać wartość 101.

 Tablica zagnieżdżona w obiekcie

Cała filozofia to zastosowanie odpowiednich nawiasów i wcześniej poznanych sposobów. Wartość "pen" z tablicy zagnieżdżonej w obiekcie przypiszemy do zmiennej tak:

const words = {
  easy: [ "pen", "gum", "box" ],
  medium: [ "spoon", "mirror" ]
}
const { easy: [ firstWord ] } = words;

Teraz zmienna firstWord przechowuje wartość "pen" (pamiętaj, że easy nie staje się zmienną).

 Obiekt zagnieżdżony w tablicy

Jeżeli mamy sytuację odwrotną: chcemy dostać się do zagnieżdżonego w tablicy obiektu, do imienia "Mia", zrobimy to tak:

const students = [
  { name: "John", age: 19 },
  { name: "Mia", age: 20 }
];
const [ , { name } ] = students;

Teraz zmienna name przechowuje wartość "Mia".

 Destrukturyzacja w parametrze funkcji

Destrukturyzację możemy stosować bezpośrednio w parametrach funkcji, gdy przy przekazywaniu argumentów spodziewamy się całego obiektu, tablicy czy stringu, ale chcemy wykorzystać tylko część przechowywanych przez nie wartości.

Dobrym przykładem jest nasłuchiwanie eventu, gdzie często wykorzystuje się tylko jego właściwość target czy currentTarget, np. tak:

button.addEventListener("click", changeColor);

function changeColor(event) {
  event.target.style.color = "red"
}

Do funkcji changeColor przekazujemy cały obiekt event, a w jej ciele korzystamy tylko z wartości właściwości target (którą jest nasz przycisk). Podczas tworzenia funkcji możemy więc od razu przypisać tę wartość do zmiennej o nazwie właściwości:

function changeColor( { target } ) {
  target.style.color = "red"
}

Z takim zapisem na pewno jeszcze się spotkasz. Nie jest jednak powiedziane, że to zawsze dobre rozwiązanie – często ważniejsza jest czytelność. Gdy ktoś spojrzy teraz na funkcję changeColor (załóżmy że w kodzie jest ona znacznie oddalona od miejsca wykorzystania), to jeśli zna JavaScript, pewnie domyśli się, że właściwość target pochodzi z obiektu event. Po co jednak wzbudzać wątpliwości? Ja zostałbym w tym przypadku przy pierwszej wersji bez destrukturyzacji.

 Destrukturyzacja, rozproszenie i parametr reszty w jednym przykładzie

Sprawdź się! (Uwaga, to może nie być proste, jeśli dopiero wdrażasz się w ten temat). Czy w poniższym kodzie potrafisz odnaleźć i prawidłowo nazwać destrukturyzację, rozproszenie i parametr reszty?

const pen = {
  color: "red",
  ink: "blue",
  brand: "Easee",
}

const penAssets = ["short", "handy", "perfect for school"];

displayData = ({color, ink}, ...assets) => {
  let desc = `This is a ${color} pen with ${ink} ink and it is: `

  assets.forEach(asset => {
    if (assets.indexOf(asset) === assets.length - 1) {
      desc = desc + "and " + asset + "."
    } else {
      desc = desc + asset + ", "
    }
  })

  console.log(desc)
}

displayData(pen, ...penAssets);

To, co widzisz powyżej, to:

  • {color, ink}destrukturyzacja w pierwszym parametrze funkcji displayData(); czyli spodziewamy się obiektu o konkretnych nazwach właściwości
  • ...assetsrest w drugim (ostatnim) parametrze funkcji displayData(), bo nie chcemy ograniczać liczby przekazanych argumentów
  • ...penAssets – spread w wywołaniu funkcji displayData(), bo postanowiliśmy zgromadzić zalety długopisu w tablicy (a nasza funkcja nie przyjmuje tablicy), zamiast wpisywać je ciągiem jako argumenty funkcji; musimy je więc „rozproszyć”.

 

Jeśli destrukturyzacja to dla Ciebie nowość, będziesz pewnie potrzebować trochę czasu, by się z nią oswoić. Tak naprawdę to kwestia zapamiętania jej składni. Jeżeli w swoich zadaniach i projektach widzisz możliwość jej zastosowania – zrób to. Praktyka czyni mistrza, a od destrukturyzacji nie ma ucieczki, prędzej czy później się na nią natkniesz.

Udostępnij ten artykuł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.