.

.

czwartek, 30 marca 2017

IIFE, scope, closures - prosto (choć w metaforach)

Pierwsze zetknięcie z IIFE zaowocowało... głupawką. ;) Instytut Idyllicznej Fascynacji Entropią? A może: Impulsywna Idiosynkrazja Fantomowych Eskimosów. Albo: Immanentny Infantylizm Filuternych Ekscentryków. Lub...

No dobrze, wystarczy już tych propozycji w spontanicznym wyzwaniu pt. "Rozwiń skrót IIFE w idiotyczny sposób, używając mądrze brzmiących słów". ;) Teraz do rzeczy.

IIFE, czyli Immediately-Invoked Function Expression. Cóż to takiego i do czego to?


Jak nazwa wskazuje, jest to wyrażenie funkcyjne wywoływane natychmiastowo, w skrócie: funkcja natychmiastowa. Jest ona wykonywana synchronicznie (tzn. od razu) i tylko jeden raz (nie można wywołać jej wielokrotnie, jak "zwykłych" funkcji). Na poziomie zapisu – to funkcja (anonimowa lub z nazwą) od razu wywołana i ujęta – wraz z wywołaniem – w nawiasy okrągłe:

(function() {
    ...
}());

IIFE ma zastosowanie wtedy, gdy chcemy zabezpieczyć zmienną/zmienne przed "wyciekiem" do zakresu globalnego (global scope). Wewnątrz IIFE można tworzyć (a w zasadzie: emulować) prywatne zmienne. Precyzując: jest to możliwe, gdy IIFE przyjmuje postać domknięcia (closure); jest to specjalny sposób na stworzenie prywatnych zmiennych, bo tego rodzaju zmienne explicite w JS nie istnieją. Ale po kolei.


Zakresy zmiennych i domknięcia (closures)


Zakres (scope) zmiennych w JS jest ograniczony do funkcji. Zmienna zadeklarowana w ramach funkcji to zmienna lokalna  – dostępna w danej funkcji i niszczona po jej wywołaniu. Zmienne lokalne pozostają niewidoczne w kodzie "na zewnątrz" funkcji, są jednak dostępne w funkcjach zagnieżdżonych (wewnętrznych).

Jeżeli wynikiem jednej funkcji jest druga funkcja (innymi słowy: gdy funkcja zwraca funkcję), mamy do czynienia ze wspomnianym domknięciem, np.:

function rodzic() {
    var x = 1;
    return function dziecko() {
        var y = 2;
        console.log(x + y);    
    }
}

Powtórzmy: funkcja zagnieżdżona (funkcja-dziecko) ma dostęp do własnych zmiennych oraz do zmiennych, które zostały zdefiniowane w funkcji zewnętrznej (funkcji-rodzicu). Co istotne: nawet po wywołaniu funkcji-rodzica, funkcja-dziecko zachowuje dostęp do zmiennych funkcji-rodzica – na tym polega istota domknięć. Rozkładając to na czynniki pierwsze:
  • funkcja-rodzic jest wykonywana – jej wynikiem jest zwrócenie (return) funkcji-dziecka – i zostaje zakończona
  • oznacza to, że kontekst jej wywołania (czyli jej wszystkie zmienne) powinien zostać zniszczony
  • tak się jednak nie dzieje, bo funkcja-dziecko (będąca wynikiem wykonania funkcji-rodzica) musi mieć dostęp do zmiennych funkcji-rodzica
  • z tego właśnie powodu zakres funkcji-rodzica nie może zostać zniszczony (bo w takim przypadku - UWAGA: makabryczna metafora – ucięlibyśmy funkcji-dziecku np. rączki)
  • wszystkie lokalne zmienne funkcji-rodzica są więc domykane (stąd nazwa "domknięcie"), czyli niejako "chwytane" przez zakres funkcji-dziecka. Niemakabryczna metafora: po wykonaniu i zakończeniu funkcji-rodzica jej scope żyje dla funkcji-dziecka. Tylko i wyłącznie dla niej. 
  • zmienne funkcji-rodzica pozostają niewidoczne dla wszystkich innych zakresów, w szczególności – dla globalnego.
    Innymi słowy: zmienne funkcji-rodzica mogą stać się dostępne w global scope lub local scope innych funkcji (innych niż funkcja-dziecko) tylko pośrednio, tj.: poprzez wywołanie funkcji-dziecka, która ma dostęp do zmiennych funkcji-rodzica. I to właśnie znaczy że zmienna jest prywatna (po zakończeniu funkcji-rodzica "należy" bezpośrednio tylko do funkcji-dziecka, do nikogo, a raczej niczego, więcej).

Dla zmęczonych tym wywodem jeszcze jedna metafora, ujmująca, jak mi się wydaje, całość zagadnienia:

Funkcja-dziecko dziedziczy po "zmarłej" funkcji-rodzicu dostęp do jej zmiennych – i od tego momentu tylko i wyłącznie ona może z nich korzystać.


Na domknięcie rozważań o domknięciach i IIFE warto dodać, że IIFE może (i często jest), ale nie musi być, domknięciem. Jak w przykładzie poniżej:

(function() {
    console.log(100);
}());


A wracając jeszcze do zmiennych... Z posta o IIFE zrobił się post o zakresach zmiennych i domknięciach, zgodnie z holistyczną koncepcją świata. ;) Zatem wracając:

Zmienna niezawarta w funkcji to zmienna globalna. Znajduje się ona w przestrzeni globalnej, co oznacza, że jest dostępna i może być modyfikowana w całym programie. Uwaga na marginesie: niejawne zadeklarowanie zmiennej wewnątrz funkcji (czyli bez użycia słowa kluczowego var), np.:

function() {
    x = 2;
    ...
}

skutkuje utworzeniem zmiennej globalnej. Taki psikus.


Tyyyyyyyyle teorii. ;)


Zastosowanie IIFE w praktyce też już było (można zobaczyć tutaj), ale to więcej niż pewne, że będę to wszystko jeszcze długo "mielić". Cóż... Clark Kent też nie od razu odkrył w sobie Supermana. ;)



PS
Z Wikipedii dowiedziałam się jeszcze, że zapis IIFE, który podałam na początku tego posta, to tzw. Douglas Crockford's style. (I że pan Douglas to ważna persona w światku JS. ;)) A także, że IIFE można zapisywać na wiele sposobów, np. owijając tylko funkcję (bez jej wywołania) w nawiasy ( ):

(function() {
    ...
})();

Albo w ogóle bez nawiasów, z dodatkowym znakiem (!, ~, -, +) przed function.


Brak komentarzy:

Prześlij komentarz