Hoisting to mechanizm silnika JavaScriptu, który podczas fazy kompilacji „wynosi” deklaracje zmiennych i funkcji na początek ich zakresu (scope), zanim kod zacznie się wykonywać linijka po linijce. W praktyce oznacza to, że możesz odwołać się do czegoś, co w kodzie zostało zadeklarowane dopiero niżej — a interpreter i tak będzie o tym wiedział. Słowo „wynoszenie” jest jednak metaforą: fizycznie nic się nie przesuwa, to silnik najpierw skanuje cały zakres i rejestruje deklaracje, a dopiero potem rusza z wykonaniem.
Jak to działa
JavaScript przetwarza kod w dwóch przejściach. W pierwszym (creation phase) rezerwuje pamięć dla wszystkich deklaracji znalezionych w danym zakresie. W drugim (execution phase) wykonuje instrukcje. Kluczowy szczegół: hoisting dotyczy deklaracji, nie przypisań. Zmienna zadeklarowana przez var jest wynoszona i od razu inicjowana wartością undefined, więc odwołanie do niej przed przypisaniem nie wyrzuci błędu — po prostu dostaniesz undefined.
Inaczej zachowują się let i const. Też są wynoszone, ale nie dostają wartości początkowej i lądują w tzw. Temporal Dead Zone (TDZ) — strefie od początku zakresu do linii deklaracji. Próba użycia ich w TDZ kończy się wyjątkiem ReferenceError. Z kolei deklaracje funkcji (function foo() {}) są wynoszone w całości, razem z ciałem, dlatego możesz je wywołać zanim pojawią się w kodzie.
Przykład z praktyki
Klasyczna pułapka, na którą trafi prawie każdy junior podczas code review w VS Code czy przy lintingu ESLintem:
console.log(x); var x = 5;wypiszeundefined— deklaracja wyniesiona, przypisanie nie.console.log(y); let y = 5;rzuciReferenceError: Cannot access 'y' before initialization— przez TDZ.powitaj();przedfunction powitaj() {...}zadziała bez problemu.- Ale
const powitaj = () => {}wywołane wcześniej już nie — bo function expression nie jest hoistowany jak deklaracja.
Częste błędy i mity
Najczęstszy mit brzmi: „kod fizycznie przeskakuje na górę”. Nie przeskakuje — silnik tylko wcześniej rejestruje nazwy. Drugi błąd to przekonanie, że let i const „nie są hoistowane”. Są — różnica polega na braku domyślnej inicjalizacji i obecności TDZ. Praktyczna rada: deklaruj zmienne na górze zakresu i używaj let/const zamiast var. Wtedy hoisting przestaje cię zaskakiwać, bo TDZ wymusza porządek, a narzędzia w stylu ESLinta wyłapią użycie przed deklaracją zanim trafi to na produkcję.
Pojęcia powiązane: scope (zasięg), Temporal Dead Zone, var/let/const, function declaration vs function expression, closure, lexical environment.