Droga za ogrodzeniem.

Domknięcia – Powtórka przed ReactJS #8

Zacznijmy od spraw natury czysto organizacyjnej. Od dnia dzisiejszego, wpisy z serii Powtórka przed ReactJS, będą miały odmienną formę niż dotychczas.

Do tej pory skupiałem się na kompleksowym omawianiu wybranych zagadnień. Takie podejście miało liczne zalety w zakresie opracowania najistotniejszych fragmentów specyfikacji ES6. Mamy końcówkę 2017 roku, a mimo to nadal trudno wskazać wyczerpujące i jednocześnie zwięzłe materiały do nauki ‚nowości’, które stały się częścią standardu przeszło dwa lata temu.

Jeżeli chodzi o tematykę zaplanowaną na drugą część serii, sprawa wygląda zupełnie inaczej. Skupiamy się na kwestiach stanowiących fundamentalną część języka od samych jego początków. Powstało wiele książek, artykułów i video tutoriali, dopracowanych do tego stopnia, że poświęcanie czasu na omawianie tych tematów byłoby równie wartościowe jak ponowne wynalezienie koła.

Od tej pory w każdym wpisie znajdziecie:

  1. Krótki, rzeczowy opis części teoretycznej zagadnienia.
  2. Analizę przykładu. Zobaczymy, jak teoria ma się do pracy z kodem.
  3. Odnośniki do paru wyselekcjonowanych, darmowych źródeł. Pozwolą one na dogłębne zaznajomienie się z tematem. Będą przeplatane zadaniami praktycznymi, żebyście nie zanudzili się na śmierć ;).
  4. Kompilację pytań. Warto sprawdzić, ile faktycznie udało się zapamiętać i zrozumieć.

Tyle, jeżeli chodzi o sprawy organizacyjne. Teraz przejdziemy do tytułowych domknięć. Zobaczymy, jak nowy format sprawdza się w praktyce.

Czym są domknięcia?

Domknięcie to funkcja, która pamięta swoje otoczenie leksykalne i może uzyskać do niego dostęp. W JS każda funkcja jest domknięciem, ponieważ przechowuje informacje o swoim środowisku leksykalnym dzięki ukrytej właściwości [[Environment]].

Jak ta definicja przekłada się na pracę z kodem? Już podczas stawiania pierwszych kroków z JS, intuicyjnie wykorzystujemy możliwość odwoływania do zmiennych globalnych z poziomu funkcji.

Czy jesteśmy tego świadomi czy nie, najprostsze domknięcia towarzyszą nam od samych początków programowania. To właśnie funkcja połączona z możliwością odwoływania się do zakresów zewnętrznych stanowi istotę domknięcia. Można powiedzieć, że funkcja domyka się na zakresie otaczającym podczas wyszukiwania zmiennej.

Aby taka mechanika miała ręce i nogi, silnik zamraża zakres funkcji otaczającej, nawet jeżeli minęło dużo czasu od jej wykonywania. Stan zmiennych jest przechowywany w pamięci, dopóki istnieje funkcja, która domyka się na tym zakresie.

Najłatwiejszym sposobem doprowadzenia do takiej sytuacji jest zwrócenie przez funkcję otaczającą nowej funkcji lub obiektu z referencją do funkcji zagnieżdżonej. Pozwala to na tworzenie modułów wykorzystujących hermetyzację danych. Mamy pełną kontrolę nad interfejsem naszego kodu.

function makeBrowser() {
var name = "Mozilla";

function displayName() {
console.log(name);
}

function changeName(newName) {
name = newName;
}

return {
displayName,
changeName
}
}

let browser1 = makeBrowser();
let browser2 = makeBrowser();

browser2.changeName('Chrome');

browser2.displayName(); // Chrome
browser1.displayName(); // Mozilla

browser1.changeName('Safari');

browser1.displayName(); // Safari
browser2.displayName(); // Chrome

W naszym przykładzie mamy do czynienia z trzema funkcjami. Pierwsza z nich to funkcja otaczająca makeBrowser. Pozostałe dwie, znajdujące się w jej wnętrzu, to funkcje zagnieżdżone: displayName oraz changeName. To właśnie one stanowią interfejs naszego modułu.

Naszym celem jest operowanie na wartości zmiennej name ze środowiska makeBrowser bez wchodzenia z nią w bezpośrednią interakcję. Każdy interfejs zwrócony przez makeBrowser posiada własny, niezależny stan. Niezależnie od tego, ile takich interfejsów stworzymy, każdy z nich będzie ‚żył własnym życiem’. Proces ten można zaobserwować dzięki zmiennym browser1 i browser2.

Na pozór wszystko wydaje się naprawdę proste. Pewnie zastanawiasz się, skąd to całe zamieszanie związane z domknięciami. Jest to jak najbardziej zrozumiałe. Sam mechanizm jest stosunkowo prosty. Sztuką jest umiejętność projektowania kodu w oparciu o sprawne zastosowanie domknięć. To przyjdzie z czasem i praktyką.

Materiały do nauki

  1. JavaScript.info – The Modern JavaScript Tutorial: Closure W pierwszej kolejności zachęcam Was do przeczytania (prawdopodobnie) najlepszego artykułu o domknięciach, jaki kiedykolwiek ujrzał światło dzienne. Ilya Kantor, twórca javascript.info, dogłębnie omawia mechanizmy związane z funkcjonowaniem środowisk leksykalnych w JS. To właśnie one decydują o takim a nie innym działaniu domknięć. Wiedza o tym, co dzieje się za kulisami, zamieni mglistą intuicję w dogłębne zrozumienie tematu. Czas potrzebny na lekturę: 25 minut.

    Po przerobieniu teorii, pozostajemy na łamach javascript.info. Zabierzemy się za przerobienie 7 praktycznych przykładów, które Ilya przygotował na końcu artykułu.

    Uważajcie, większość z ćwiczeń jest podchwytliwa. Dobrze zastanówcie się, czy jesteście przekonani co do swojej odpowiedzi przed kliknięciem w przycisk ‚Solution’.

    Autor nie rozwodził się nad rozwiązaniami, więc w razie jakichkolwiek wątpliwości, polecam przerzucić przykład do DevTools. Po przeanalizowaniu kodu, linijka po linijce, wszystko powinno być jasne. Zwracajcie uwagę na to, co się dzieje w zakładce Scope.

  2. You Don’t Know JavaScript – Scope & Closures: Chapter 5 Jako materiał uzupełniający wykorzystamy jedną z najpopularniejszych książek wśród programistów JS. Co ciekawe, Kyle Simpson znany ze swojego dociekliwego badania wszystkich zakamarków JavaScript, potraktował tytułowe domknięcia dość pobieżnie. Jest to zrekompensowane dogłębną analizą kilku naprawdę ciekawych przykładów. Czas potrzebny na lekturę: 25 minut.Na koniec polecam przepracować następujące 13 zadań: jsFiddle. Tym razem będziemy pisali kod od podstaw. Pierwsze 7 zadań opiera się na rozbudowywaniu funkcji wykorzystującej domknięcia. Pozostałe przykłady są skoncentrowane na operacjach z czasem i datami.

Sprawdź się – lista pytań

Okej, na koniec mam dla Was listę pytań. Jeżeli uważnie zapoznaliście się z Materiałami do nauki, to nie powinno być problemów z udzieleniem poprawnych odpowiedzi.

  1. Czym jest środowisko leksykalne (Lexical Environment)?
  2. Z czego składa się środowisko leksykalne?
  3. Jaka jest zewnętrzna referencja globalnego środowiska leksykalnego?
  4. Czym, w kontekście środowiska leksykalnego, jest zmienna?
  5. Kiedy dochodzi do przetwarzania deklaracji funkcji?
  6. Kiedy dochodzi do przetworzenia zmiennych ‚let/const’?
  7. Co się dzieje, w kontekście środowisk leksykalnych, podczas wywołania funkcji?
  8. Co jest wspólne dla wszystkich środowisk leksykalnych?
  9. Jak przeszukiwane są środowiska leksykalne podczas próby uzyskania dostępu do zmiennej?
  10. Co się dzieje, w strict mode, gdy wyszukiwanie zmiennych kończy się niepowodzeniem?
  11. Czy wywołanie funkcji ZAWSZE prowadzi do powstania nowego środowiska leksykalnego?
  12. Jakim typem obiektu jest [[LexicalEnvironment]]. Jak możemy go modyfikować?
  13. Jaką właściwość, związaną z środowiskiem leksykalnym, otrzymuje każda funkcja w momencie utworzenia?
  14. Czy sposób utworzenia funkcji (użycie deklaracji funkcji oraz wyrażenie funkcyjnego), ma wpływ na środowisko leksykalne?
  15. Czym jest domknięcie?
  16. Czy środowisko leksykalne powstaje dla bloków kodu?
  17. Czy środowisko leksykalne powstaje dla pętli for?
  18. Czy istnieje możliwość natychmiastowego wywołania deklaracji funkcji?
  19. Jak stworzyć natychmiastowe wywołane wyrażenie funkcyjne?
  20. Czy środowiska leksykalne podlegają takim samym zasadom zbierania nieużytków co inne obiekty?

Jeżeli chcecie, żeby ta wiedza została z Wami na stałe, to zapraszam do zapoznania się z moim sposobem na niesamowitą pamięć. Połowę pracy wykonałem za Was, więc macie ułatwioną drogę do stworzenia pierwszych programistycznych fiszek :).

Osoby mające problem z samodzielnym udzieleniem odpowiedzi zachęcam do zadawania pytań w komentarzach.

Tyle na dziś

Zapraszam do polubienia strony bloga na fejsie i śledzenia mnie na Twitterze. W ten sposób będziecie na bieżąco z moją działalnością, a ja otrzymam bezcenny zastrzyk pozytywnej energii ;).

Nie zapomnijcie o regularnym odwiedzaniu portalu Polski Front-end. Znajdziecie tam codzienną porcję wpisów, tworzonych przez szerokie grono programistek i programistów o różnym stopniu wtajemniczenia w arkana frontendu.

Do zobaczenia za tydzień, zabierzemy się za kolejny must-know każdego adepta JS – wskaźnik this.

Zdjęcie tytułowa autorstwa: Ibrahim Boran

Marcin Czarkowski

Cześć! Ja nazywam się Marcin Czarkowski, a to jest AlgoSmart - blog, na którym dzielę się wiedzą o ReactJS oraz JavaScript. Tworzę poradniki na YouTube i jestem współtwórcą przeprogramowani.pl

  • nsu

    „Aby taka mechanika miała ręce i nogi, silnik zamraża zakres funkcji
    otaczającej, nawet jeżeli minęło dużo czasu od jej wykonywania”

    Z moich prób wynika, że nie „zamraża”, zakres jest zmienny dla wszystkich domknięć na danym interfejsie. Niżej przykład:

    function state(s) {

    let state = s
    return {
    get: function () {

    return state
    },
    set: function(value) {

    state = value
    },
    reset: function () {

    state = 0
    }
    }
    }
    const myState = state(5) // Obiekt dostarczający Trzy doknięcia

    console.log(myState.get()) //

    myState.set(10) // każde z tych domknięć zmienia stan, a efekty są widoczne w innych

    myState.reset() //

    console.log(myState.get()) //

    Być może źle rozumuję, więc proszę mnie poprawić, bo dopiero się uczę. Ale zamrożenie wyobrażam sobie jako coś co nie może się zmieniać. Dobry przykład na „zamrażanie” jest na podanym przez Ciebie linku do javascript.info w ostatnim tasku. Tam rzeczywiście każde domknięcie umieszczone w tablicy musi zwracać konkretą wartość iteratora z pętli. Możliwe jest to poprzez zdefiniowanie dodatkowej zmiennej lokalnej, co w środowisku [[Scopes]] dodaje kolejny typ zasięgu – blokowego (oprócz closure). Wydaję mi się, że elementy środowiska są utrzymywane dopóki wiąże ich referencja do nich z wnętrza domknięcia. I to jest istotą całego zamieszania wokół domknięć. Tak jak napisałem, dopiero się uczę, więc zależałoby mi na tym, żeby ktoś się do tego ustosunkował. Z góry dzięki.