This page is available only in polish

I am working on the translation!

Zrozumieć hoisting

Niektóre koncepty Javascriptu wydają się nietypowe, żeby nie powiedzieć dziwne. Dziś chciałbym pochylić się nad jednym z takich mechanizmów a mianowicie hoistingiem.

Hoisting PAGES.POST.COVER_THUMBNAIL.BY_WHOM Likoper

From this article you will learn:

  • Co to jest hoisting?

  • Jakie są różnice pomiędzy zasięgiem dynamicznym a leksykalnym?

  • Co to jest leksykalizacja i tokenizacja w JS?

  • Czy let i const również podlegają zjawisku hoistingu?

  • Co to jest Temporal Dead Zone?

Javascript jest ciekawym językiem. Ma zaimplementowanych wiele konceptów, które dla osób, które pierwszy raz się z nim stykają, mogą wydawać się dziwne, może nawet nieintuicyjne. Jednym z takich mechanizmów jest hoisting.

Hoisting to proces, który polega na określaniu zasięgu w jakim ma być dostępna zmienna. W większości języków programowania zmienna dostępna jest w bloku, w którym została zadeklarowana, a ponadto może być użyta dopiero po jej deklaracji, ale w Javascript jest inaczej.

Zanim przejdziemy do omówienia hoistingu w szczegółach, warto wspomnieć o zasięgu zmiennych i funkcji.

Zasięg dynamiczny i leksykalny

Zasięg, czyli scope, to nic innego jak obszar w ramach, którego dostępne są zmienne czy też wyrażenia. W programowaniu wyróżnia się dwa rodzaje zasięgów: dynamiczne i leksykalne.

Dynamiczny zasięg nie jest najpopularniejszym konceptem w dzisiejszym świecie IT. W zasadzie nie występuje w językach opartych o język C, takich jak JS. Pierwotnie został wprowadzony w języku Lisp, ale można się z nim spotkać również w takich językach jak bash czy LaTeX. Natomiast w Perl, pomimo że język domyślnie opiera się o zasięg leksykalny, można wybrać zasięg dynamiczny.

Na czym polega dynamiczność zasięgu? W zasięgu dynamicznym program szuka odpowiedniego bloku (pętli, warunku, funkcji), w którym występuje dane wyrażenie lub zmienna, a następnie sukcesywnie przechodzi do kolejnych funkcji wywołujących i na ich podstawie określa wartość wyrażenia lub zmiennej. Innymi słowy w zasięgu dynamicznym wartość zmienia się w zależności od kolejności wywoływania funkcji.

Javascript jest językiem, który opiera swoją składnię o język C. Co prawda Brendan Eich tworząc JSa bazował na wielu różnych językach: C, Modula-2 czy Java. Samo pochodzenia może nam już powiedzieć, że koncepcja zasięgu została w nim wprowadzona podobnie jak w językach, na których bazowano podczas jego projektowania. C, Java czy Javascript wykorzystują zasięg leksykalny.

Zasięg leksykalny (nazywany również zasięgiem statycznym) opiera się o lokalizację funkcji i zmiennych w czasie kompilacji. Oznacza to, że wartość zmiennej nie zmieni się w zależności od kolejnych wywołań funkcji w czasie rzeczywistym. Zasięg leksykalny jest zdecydowanie łatwiejszy do opanowania przez programistę. Bardzo często wystarczy przeczytać fragment kodu, aby wywnioskować wartość bez uwzględniania różnych kontekstów wywołań, które mogłyby na nią wpłynąć.

Poniższy przykład pokazuje jak działa zasięg leksykalny. Zmienna x jest zależna tylko od bloku, którym została zadeklarowana. Nie ma znaczenia gdzie została wywołana funkcja, która zwraca wartość zmiennej x. Gdyby JS posiadał dynamiczne zasięgi wynikiem wywołania funkcji bar, byłaby wartość 2.

Hoisting

W języku Javascript zmienne możemy deklarować na 3 sposoby, a funkcje na 2 sposoby. Jeśli chodzi o deklarowanie zmiennych mamy do dyspozycji var, let oraz const, w przypadku funkcji możemy wykorzystać słowo kluczowe function lub zapis arrow function. Na początku skupmy się na działaniu hoistingu w przypadku słów kluczowych var oraz function.

Jak już wspomniałem hoisting polega na określaniu zasięgu, w którym dana wartość ma być dostępna. Można śmiało powiedzieć, że mechanizm ten przenosi deklaracje zmiennych na początek ich zasięgu jeszcze przed wykonaniem kodu. Sprowadza się to do sytuacji, w której zmienne będą dostępne nawet przed ich deklaracją. Spójrzmy na poniższe przykłady:

Ta sama funkcja została zapisana w dwóch różnych językach. W obu przypadkach kolejność deklaracji i odniesień do zmiennej jest taka sama. Różnica polega na tym, że Javascript (pierwszy przykład) określa zmienną jako wartość undefined (co ważne, nie zwraca błędu), a Java zwraca błąd. Co ciekawe ten sam mechanizm działa również na funkcje zapisane za pomocą słowa kluczowego function.

Podsumowując hoisting sprawi, że zmienna będzie dostępna i może być wykorzystana nawet przed jej zadeklarowaniem.

Zasięgi leksykalne w Javascript

Warto wspomnieć o tym, że w Javascript występują różne rodzaje zasięgów leksykalnych. Upraszczając temat możemy wyodrębnić 3 podstawowe: zasięg globalny (w przeglądarce powiązany z obiektem window), zasięg leksykalny funkcji oraz zasięg leksykalny bloku. Zasięgi blokowe zostały wprowadzone do JSa wraz z nowymi sposobami deklaracji zmiennych za pomocą let oraz const. Można powiedzieć, że zasięg funkcji jest odmianą zasięgu blokowego. Różnica polega na tym, że zmienne deklarowane za pomocą var w innych blokach (np. pętlach czy warunkach) są traktowane jako przynależne do bloku funkcji, w której zostały zadeklarowane. Natomiast zmienne deklarowane za pomocą let czy const są przynależne do dowolnego bloku, w którym zostały zadeklarowane. Spójrzmy na przykład:

let i const vs hoisting

Panuje mylne przekonanie, że na let i const nie działa mechanizm hoistingu. Wynika to z faktu, że JS wprowadził dodatkowy mechanizm sprawdzania odwołań do zmiennych deklarowanych w ten sposób. Jeśli odwołamy się do zmiennej zadeklarowanej za pomocą let lub const przed jej deklaracją otrzymamy wiele mówiący komunikat: „Cannot access variable before initialization”. Gdyby mechanizm hoistingu nie działał otrzymalibyśmy błąd, podobny do tego, gdy odwołamy się do zmiennej bez jej deklaracji. Zobaczmy przykład:

Jak widać mamy do czynienia z dwiema różnymi sytuacjami, w przypadku zmiennej b Javascript wie o jej istnieniu, mówi natomiast że nie możemy jej użyć przed jej zainicjowaniem.

Przestrzeń pomiędzy dostępnością zmiennej a odwołaniem do niej to tak zwana Temporal Dead Zone.

Kiedy odbywa się hoisting?

Javascript jest językiem interpretowanym. Języki interpretowane wykorzystują specjalne programy, tzw. interpretery do analizy kodu. Kod analizowany jest linijka po linijce, kod maszynowy, który powstaje w tym procesie, nie jest nigdzie zapisywany. Najczęściej języki interpretowane są wolniejsze niż kompilowane. Kod wykonywany jest z pamięci jako część całego procesu interpretowania kodu. Skoro już wiemy mniej więcej co się dzieje z Javascriptem, to w takim razie w którym momencie dzieje się hoisting?

Przede wszystkim trzeba zwrócić uwagę na to, że Javascript w większości przypadków uruchamiany jest w przeglądarkach. Nowoczesne przeglądarki wykorzystują technologię kompilacji JIT (Just-In-Time), która oznacza, że kod jest przekształcany do wykonywalnego kodu maszynowego, a następnie uruchamiany. Silniki Javascriptowe (takie jak V8 czy SpiderMonkey) w zasadzie łączą w sobie cechy interpreterów i kompilatorów. Kod JS przechodzi w silniku JS przez dwie fazy. Pierwsza to faza kompilacji, w czasie której wystąpi zjawisko hoistingu zmiennych. W zasadzie sprowadza się to do dwóch procesów: leksykalizacji i tokenizacji. W szczegółach, jest to nic innego jak dzielenie kodu na mniejsze tokeny, z których następnie tworzone jest tak zwane AST (Abstract Syntax Tree) dla analizowanego zasięgu.

W tym momencie, gdy silnik napotka deklarację zmiennej przydziela jej pamięć, nie modyfikuje kodu. Powstaje w ten sposób wiązanie pomiędzy zmienną a pamięcią. W trakcie przydzielania pamięci do zmiennej zostaje do niej przypisana wartość domyślna, czyli undefined. Następnie jest ustalany zasięg na podstawie przypisań i dopiero wtedy generowany jest kod maszynowy.

Podsumowanie

Hoisting jako zjawisko jest opisany jednym zdaniem, a jak widać można o nim powiedzieć troszkę więcej. Pojęcie to zostało ukute przez programistów, aby prościej wyjaśnić cały proces alokacji pamięci dla zmiennych w czasie fazy kompilacji kodu Javascriptowego. Jak widać nic nie dzieje się bez przyczyny - a sama specyfika tego zjawiska jest bezpośrednio powiązana z tym, jak działa i gdzie wykorzystywany jest Javascript.

Share this article:

Comments (1)

  • tworzeniestroninternetowych_warszawa_pl

    31 sierpnia 2023 o 10:31

    Tekst wyjaśla hoisting w języku JavaScript w sposób przejrzysty. Mechanizm ten przenosi deklaracje zmiennych i funkcji na początek ich zasięgu, co może być mylące, szczególnie dla początkujących programistów. Wysuwa się istotne kontrasty pomiędzy zasięgami dynamicznym i leksykalnym. Hoisting wzbogaca zrozumienie tego, jak kod jest kompilowany i wykonywany w JavaScript. Pomimo że hoisting jest użytecznym mechanizmem, ważne jest, aby unikać niejednoznacznych deklaracji i korzystać z niego w sposób umiejętny.

You may be interested in

lorem

Klamerki by tolgart
Article
09 May 2023

Wszystko o klamrach w JS

W kodzie znajdziemy bardzo dużo różnych znaków, które pozwalają nam na rozróżnianie poszczególnych jego elementów. Na szczególną według mnie uwagę zasługują nawiasy klamrowe, pełniące różnorakie funkcje w kodzie JS.

Read more
undefined by undefined
Article
15 January 2023

Moduły w JS

Trudno wyobrazić sobie aplikację, która składa się z jednego pliku. Trudno wyobrazić sobie rozwijanie takiego kodu i jego późniejsze utrzymywanie. Na szczęście Javascript obsługuje moduły, ale nie zawsze tak było.

Read more
Editor by Maica
Article
09 July 2020

Jak zbudować własny edytor tekstu w przeglądarce?

Praca programisty polega na rozwiązywania problemów. Języki programowania są naszymi narzędziami. Używając nich możemy tworzyć nowe rozwiązania, ale też nowe narzędzia. Zdecydowanie edytory tekstu są takimi narzędziami. Dziś spróbujemy napisać swój własny.

Read more

Zapisz się do newslettera

Bądź na bieżąco z nowymi materiałami, ćwiczeniami i ciekawostkami ze świata IT. Dołącz do mnie.