Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Hoisting w JavaScript

Witaj z powrotem w Parku Jurajskim, @name! W poprzedniej lekcji sprawdzaliśmy, gdzie żyje zmienna, czyli jaki ma zasięg. Dzisiaj odpowiemy na pytanie równie ważne: od którego momentu zmienna albo funkcja w ogóle istnieje. Mechanizm, który o tym decyduje, nazywa się hoistingiem, a jego znajomość chroni przed najbardziej podstępną kategorią błędów w systemach parku - takich, które niczego nie wysadzają w powietrze, tylko po cichu podają złą wartość. Zmienna, która istnieje, ale nic nie zawiera, bywa groźniejsza niż zmienna, której nie ma wcale.

Czym jest hoisting?

Hoisting (po polsku "wynoszenie") to zachowanie JavaScriptu polegające na przeniesieniu deklaracji zmiennych i funkcji na górę ich zasięgu - funkcyjnego albo globalnego - jeszcze w fazie kompilacji, zanim wykona się pierwsza linia kodu. Silnik najpierw przegląda cały kod i spisuje, jakie nazwy w nim występują, a dopiero potem zaczyna go wykonywać. Dzięki temu do części zmiennych i funkcji możemy odwołać się wcześniej, niż zostały zapisane w pliku.

To trochę jak spis inwentarza sporządzany, zanim dinozaury trafią do zagród: kompilator JavaScriptu najpierw notuje, "czego się spodziewać", a dopiero potem wykonuje konkretne operacje, linia po linii. Na górę wędrują wyłącznie deklaracje - nigdy wartości, które im przypisujemy.

Hoisting funkcji

Deklaracje funkcji wynoszone są w całości: razem z nazwą podróżuje też ciało funkcji. Możemy więc wywołać funkcję, zanim pojawi się w pliku. W zarządzaniu parkiem to bardzo wygodne, bo pozwala trzymać główną logikę na górze, a szczegóły techniczne spychać na dół.

1// Mozemy wywolac funkcje przed jej zdefiniowaniem
2checkSecurity("Zagroda T-Rex");  // "Sprawdzam bezpieczenstwo: Zagroda T-Rex"
3
4// Deklaracja funkcji jest hoistowana
5function checkSecurity(location) {
6  console.log(`Sprawdzam bezpieczenstwo: ${location}`);
7  return true;
8}

Działa to dlatego, że w fazie kompilacji JavaScript wynosi na górę zasięgu całą deklarację - nazwę wraz z ciałem. Zanim wykona się pierwsza instrukcja,

checkSecurity
jest już kompletną, gotową do wywołania funkcją, a nie pustym miejscem czekającym na uzupełnienie. To jedyny przypadek w całym języku, w którym na górę zasięgu trafia razem z nazwą także wartość, bo funkcja też jest wartością. Wszystkie pozostałe konstrukcje, które za chwilę zobaczymy, wynoszą samą nazwę i nic poza nią.

Uwaga na wyrażenia funkcyjne!

W całości wynoszone są tylko deklaracje funkcji. Wyrażenia funkcyjne, czyli funkcje przypisane do zmiennej, podlegają regułom hoistingu zmiennych: sama zmienna trafia na górę, ale przypisana do niej funkcja już nie.

1// To NIE zadziala prawidlowo
2// feedDinosaur("Rex");  // TypeError: feedDinosaur is not a function
3
4// Hoistowana jest tylko deklaracja zmiennej, nie przypisanie funkcji
5var feedDinosaur = function(dinoName) {
6  console.log(`Karmienie dinozaura: ${dinoName}`);
7};
8
9// Od tej linii mozemy juz wywolac funkcje
10feedDinosaur("Rex");  // "Karmienie dinozaura: Rex"

Przeczytaj uważnie komunikat błędu, bo to cenna wskazówka. Nie dostajemy

ReferenceError
, tylko
TypeError: feedDinosaur is not a function
- nazwa
feedDinosaur
już istnieje, po prostu trzyma w tym momencie
undefined
, a
undefined
nie da się wywołać jak funkcji. Ta drobna różnica potrafi skrócić debugowanie z godziny do minuty:
ReferenceError
kieruje nas do literówki albo zapomnianego importu, a
TypeError
do kolejności linii w pliku. Deklaracja funkcji jest dostępna w całym swoim zasięgu, wyrażenie funkcyjne dopiero od linii z przypisaniem.

Funkcje strzałkowe działają tak samo

Funkcje strzałkowe również są wyrażeniami, więc ich ciało nigdy nie jest wynoszone. Ponieważ przypisujemy je zwykle do

const
albo
let
, błąd przy zbyt wczesnym wywołaniu okazuje się jeszcze bardziej stanowczy niż przy
var
.

1// checkFence();  // ReferenceError: Cannot access checkFence before initialization
2
3const checkFence = () => "Ogrodzenie OK";
4
5console.log(checkFence());  // "Ogrodzenie OK"

Różnica jest istotna: przy

var
dostaliśmy
TypeError
, tutaj
ReferenceError
. Oba komunikaty znaczą to samo - "za wcześnie" - ale wersja z
const
zatrzymuje program dokładnie w miejscu pomyłki, zamiast puścić dalej wartość
undefined
, która wybuchnie kilkadziesiąt linii później, w zupełnie niewinnym fragmencie kodu. Stąd praktyczna reguła: przed linią definicji można używać wyłącznie funkcji zadeklarowanych przez
function nazwa() {}
. Wszystko, co przypisujemy do zmiennej - klasyczne wyrażenie funkcyjne czy strzałka - działa dopiero poniżej.

Hoisting zmiennych

Przy zmiennych hoisting zachowuje się różnie, zależnie od słowa kluczowego (

var
,
let
,
const
). Stąd bierze się większość niespodzianek, więc rozłóżmy oba przypadki na czynniki pierwsze.

Hoisting zmiennych deklarowanych przez var

Zmienne deklarowane przez

var
są wynoszone i od razu inicjalizowane wartością
undefined
, więc odczyt przed deklaracją jest w pełni legalny:

1console.log(dinosaurCount);  // undefined (nie blad!)
2var dinosaurCount = 15;
3console.log(dinosaurCount);  // 15

Nic się nie zepsuło - i na tym właśnie polega problem. Program leci dalej z wartością, której nikt nie zaplanował, a

undefined
w liczniku dinozaurów zamienia się w
NaN
w każdym obliczeniu, do którego trafi. Raport pokaże zero zwierząt w zagrodzie, w której siedzą trzy, i nikt nie dostanie żadnego ostrzeżenia. Łatwiej to zrozumieć, gdy wyobrazimy sobie, że przed uruchomieniem JavaScript przepisuje powyższy kod do takiej postaci:

1var dinosaurCount;           // Hoisting - deklaracja wedruje na gore
2console.log(dinosaurCount);  // undefined
3dinosaurCount = 15;          // Przypisanie zostaje tam, gdzie je napisalismy
4console.log(dinosaurCount);  // 15

Deklaracja wskoczyła na górę, przypisanie zostało dokładnie tam, gdzie je zapisaliśmy. Cała reguła hoistingu mieści się w jednym zdaniu: deklaracja wędruje w górę, wartość nie rusza się z miejsca. Odcinek między tymi dwoma punktami to okno, w którym zmienna jest osiągalna, ale pusta. Czujnik parku raportujący

undefined
wygląda w logach niepokojąco podobnie do czujnika, który nie raportuje niczego - a to dwie zupełnie różne sytuacje, jedna oznacza spokój, druga zerwane ogrodzenie.

Hoisting zmiennych deklarowanych przez let i const

Zmienne deklarowane przez

let
i
const
(wprowadzone w ES6) także są hoistowane, ale nie dostają wartości
undefined
. Zamiast tego trafiają do temporal dead zone, czyli martwej strefy czasowej - okresu od rozpoczęcia wykonywania bloku aż do linii, w której zmienna faktycznie zostaje zadeklarowana. Sięgnięcie po nią w tym czasie kończy się błędem.

1// To spowoduje blad:
2// console.log(securityLevel);  // ReferenceError: Cannot access securityLevel before initialization
3let securityLevel = "Wysoki";
4console.log(securityLevel);  // "Wysoki"
5
6// To samo dotyczy const:
7// console.log(parkName);  // ReferenceError: Cannot access parkName before initialization
8const parkName = "Jurassic Park";
9console.log(parkName);  // "Jurassic Park"

Dwa komunikaty błędów trzeba umieć od siebie odróżnić:

  • zmienna, która nigdy nie została zadeklarowana:
    ReferenceError: xyz is not defined
  • zmienna
    let
    lub
    const
    w temporal dead zone:
    ReferenceError: Cannot access 'xyz' before initialization

To rozróżnienie jest prezentem dla debugującego. Pierwszy przypadek oznacza, że zmiennej nie ma nigdzie - najpewniej literówka albo zapomniany import. Drugi mówi, że zmienna istnieje, tylko sięgnęliśmy po nią za wcześnie, więc wystarczy przenieść odczyt poniżej deklaracji. Wyobraź sobie funkcję na dwieście linii, w której wartość jest czytana na górze, a przypisywana na dole: z

var
dostajemy ciche
undefined
i godzinę szukania po omacku, z
let
program zatrzymuje się natychmiast i wskazuje palcem konkretną linię. Temporal dead zone to nie ograniczenie, tylko system wczesnego ostrzegania.

Klasy też siedzą w temporal dead zone

Klasy zachowują się jak

let
i
const
, a nie jak deklaracje funkcji. Ich nazwa jest wynoszona, ale dopóki silnik nie wykona ciała klasy, każda próba użycia kończy się błędem.

1// const dino = new Dinosaur("Rex");  // ReferenceError: Cannot access Dinosaur before initialization
2
3class Dinosaur {
4  constructor(name) {
5    this.name = name;
6  }
7}
8
9const dino = new Dinosaur("Rex");  // OK
10console.log(dino.name);  // "Rex"

Reguła "funkcje można definiować poniżej miejsca użycia" nie rozciąga się więc na klasy - klasa musi powstać przed pierwszym

new
, które tworzy jej instancję. Jeśli z przyzwyczajenia zepchniesz definicję klasy na koniec pliku, to właśnie ten komunikat zobaczysz w konsoli, a lekarstwo jest banalne: przenieś klasę nad kod, który ją instancjonuje. W plikach parku daje to prostą kolejność - najpierw modele zwierząt i urządzeń, potem logika, która z nich korzysta, i nigdy odwrotnie.

Hoisting a zasięg zmiennych

Hoisting jest ściśle spleciony z zasięgiem zmiennych, nazywanym też ich zakresem. Deklaracje nie wędrują na górę pliku, tylko na górę swojego zasięgu, a to, który zasięg jest "swój", zależy od słowa kluczowego:

var
trzyma się zasięgu funkcyjnego,
let
i
const
- blokowego. W zasięgu globalnym reguła pozostaje ta sama, tyle że górą jest po prostu początek programu.

Przykład dla var (zasięg funkcyjny)

var
całkowicie ignoruje klamry
if
-a czy pętli: deklaracja ląduje na górze funkcji, która ją zawiera, niezależnie od tego, w którym miejscu ją zapisaliśmy.

1function monitorSystem() {
2  console.log(status);  // undefined (hoisting w obrebie funkcji)
3
4  if (true) {
5    var status = "Online";
6  }
7
8  console.log(status);  // "Online"
9}
10
11monitorSystem();
12// console.log(status);  // ReferenceError: status is not defined (poza zasiegiem funkcji)

Blok

if
w ogóle nie utworzył tutaj nowego zasięgu dla
status
. Deklaracja wskoczyła na górę
monitorSystem
, więc zmienną da się odczytać jeszcze przed blokiem (jest wtedy pusta), a po bloku trzyma już przypisaną wartość. Poza funkcją znika kompletnie, bo jedyną granicą, którą
var
respektuje, jest funkcja. Dwa
console.log
z tą samą nazwą dają zatem dwa różne wyniki - nie dlatego, że zmienne są dwie, tylko dlatego, że patrzymy na jedną zmienną raz przed przypisaniem, a raz po nim.

Pętle pokazują tę różnicę najwyraźniej

Najlepszym poligonem do porównania obu zasięgów są pętle, bo licznik jest w nich deklarowany i używany w jednym miejscu. Sprawdź, co zostaje po pętli, w której licznik powstaje przez

var
, a co po pętli z
let
.

1for (var index = 0; index < 3; index++) {
2  // obchod zagrod
3}
4console.log(index);  // 3 - var przezywa petle (zasieg funkcyjny)
5
6for (let step = 0; step < 3; step++) {
7  // obchod zagrod
8}
9// console.log(step);  // ReferenceError - let zostaje w zasiegu blokowym

Po zakończeniu pierwszej pętli

index
nadal ma wartość
3
, bo należy do zasięgu funkcyjnego i spokojnie przeżywa zamykającą klamrę. Zmienna
step
z drugiej pętli ma zasięg blokowy i znika razem z pętlą, więc odczyt poniżej kończy się błędem
ReferenceError
. Ta różnica stoi za słynnymi kłopotami z licznikiem w pętlach z opóźnieniem: przy
var
wszystkie wywołania widzą tę samą, końcową wartość, przy
let
każda iteracja dostaje własną kopię licznika i zachowuje swoją.

Przykład dla let i const (zasięg blokowy)

Przy

let
i
const
każda para klamer tworzy nowy zasięg, a temporal dead zone dotyczy właśnie tego bloku, a nie całej funkcji.

1function checkEnclosures() {
2  // console.log(enclosureStatus);  // ReferenceError (temporal dead zone)
3
4  if (true) {
5    let enclosureStatus = "Secure";
6    console.log(enclosureStatus);  // "Secure"
7  }
8
9  // console.log(enclosureStatus);  // ReferenceError (poza zasiegiem bloku)
10}
11
12checkEnclosures();

Obie zakomentowane linie skończyłyby się błędem, ale z zupełnie różnych powodów: pierwsza dlatego, że zmienna siedzi jeszcze w temporal dead zone, druga dlatego, że blok

if
się skończył i zmienna przestała istnieć. Klamry są tu prawdziwym murem i dokładnie o to nam chodzi - zmienna blokowa zostaje w swojej zagrodzie. W ćwiczeniu, które czeka Cię zaraz po tej lekcji, poćwiczysz zasięg zmiennych w różnych kontekstach: stworzysz zmienne z różnym zasięgiem - globalnym, funkcyjnym i blokowym - i będziesz obserwować ich zachowanie.

Praktyczne przykłady hoistingu w Parku Jurajskim

Przykład 1: System monitorowania dinozaurów

Hoisting funkcji pozwala uporządkować plik według ważności, a nie według kolejności wykonania. Główna procedura idzie na górę, funkcje pomocnicze, z których korzysta, na sam dół - i wszystko nadal działa.

1// System monitorowania dinozaurow
2
3// Funkcje glowne sa zdefiniowane na gorze dla przejrzystosci
4function initMonitoringSystem() {
5  console.log("Inicjalizacja systemu monitorowania dinozaurow...");
6
7  // Mozemy wywolac te funkcje, choc sa zdefiniowane pozniej
8  const healthStatus = checkDinosaurHealth("Rex");
9  const locationData = trackDinosaur("Blue");
10
11  return {
12    health: healthStatus,
13    location: locationData,
14    systemStatus: "Online"
15  };
16}
17
18// Wywolanie glownej funkcji
19const systemStatus = initMonitoringSystem();
20console.log(systemStatus);
21
22// Funkcje pomocnicze zdefiniowane na dole pliku
23function checkDinosaurHealth(dinoName) {
24  console.log(`Sprawdzanie stanu zdrowia dinozaura: ${dinoName}`);
25  return "Zdrowy";
26}
27
28function trackDinosaur(dinoName) {
29  console.log(`Sledzenie dinozaura: ${dinoName}`);
30  return { x: 135, y: 270, area: "Sektor B" };
31}

Wywołanie

initMonitoringSystem
wypada w linii, do której żadna z funkcji pomocniczych jeszcze "nie doszła", a mimo to kod wykonuje się bez zgrzytu. To czysty hoisting deklaracji funkcji: obie były kompletne, zanim ruszyła pierwsza instrukcja. W czasie działania programu nic się już magicznie nie przesuwa, a zysk jest czysto ludzki - kto otworzy ten plik, najpierw przeczyta, co system robi, a dopiero potem, w jaki sposób.

Przykład 2: Pułapka hoistingu

Ten sam mechanizm potrafi zadziałać przeciwko nam. Hoisting staje się zdradliwy, gdy funkcja wewnętrzna deklaruje zmienną o nazwie, która istnieje już w zasięgu zewnętrznym. Przeczytaj kod uważnie i spróbuj przewidzieć wynik, zanim zajrzysz do wyjaśnienia.

1// Pulapka zwiazana z hoistingiem
2
3// Funkcja sprawdzajaca zabezpieczenia
4function checkSecurity() {
5  var fenceStatus = "Offline"; // Hoistowana na gore funkcji
6
7  function enableFences() {
8    // Lokalna zmienna jest hoistowana na gore enableFences,
9    // ale az do linii inicjalizacji nie ma zadnej wartosci
10    console.log("Aktualny status plotu przed zmiana:", fenceStatus); // undefined!
11
12    // Lokalna zmienna fenceStatus przeslania te z wyzszego zakresu
13    var fenceStatus = "Online";
14    console.log("Nowy status plotu:", fenceStatus); // "Online"
15  }
16
17  enableFences();
18  console.log("Status plotu w glownej funkcji:", fenceStatus); // "Offline" (nie zmieniono)
19}
20
21checkSecurity();

Kryje się tu subtelna pułapka:

console.log
wewnątrz
enableFences
wypisuje
undefined
, ponieważ lokalna zmienna
fenceStatus
została wyniesiona na górę funkcji wewnętrznej i od pierwszej linii przesłania tę z zasięgu zewnętrznego, choć wartość dostaje dopiero niżej. Zewnętrzna zmienna również nigdy się nie zmienia - do końca ma wartość
"Offline"
. W prawdziwym parku wygląda to tak, że panel operatora ogłasza włączone ogrodzenia, a główna funkcja bezpieczeństwa nadal uważa je za wyłączone.

Przykład 3: Lepsze podejście z let i const

Zamiana

var
na
let
i
const
likwiduje całą tę klasę problemów, bo zbyt wczesny odczyt zatrzymuje program, zamiast po cichu produkować
undefined
.

1// Lepsze podejscie z uzyciem let i const
2
3function parkOperations() {
4  // Uzywamy const dla wartosci, ktore nie powinny sie zmieniac
5  const maxVisitors = 2000;
6
7  // Uzywamy let dla zmiennych, ktore beda sie zmieniac
8  let currentVisitors = 0;
9
10  function admitVisitors(count) {
11    // Przy let i const odczyt zmiennej przed deklaracja konczy sie bledem,
12    // co znacznie ulatwia wychwycenie problemu
13
14    // To by spowodowalo blad:
15    // console.log(availableSpace);  // ReferenceError: Cannot access before initialization
16
17    // Najpierw deklarujemy, potem uzywamy
18    const availableSpace = maxVisitors - currentVisitors;
19    if (count <= availableSpace) {
20      currentVisitors += count;
21      console.log(`Przyjeto ${count} odwiedzajacych. Aktualnie w parku: ${currentVisitors}`);
22      return true;
23    }
24
25    console.log(`Za duzo odwiedzajacych! Dostepne miejsca: ${availableSpace}`);
26    return false;
27  }
28
29  // Testujemy nasza funkcje
30  admitVisitors(500);  // Przyjeto 500 odwiedzajacych
31  admitVisitors(1000); // Przyjeto 1000 odwiedzajacych
32  admitVisitors(700);  // Za duzo odwiedzajacych! Dostepne miejsca: 500
33
34  return currentVisitors;
35}
36
37console.log(`Laczna liczba odwiedzajacych: ${parkOperations()}`);

Sprawdźmy, co się tu nie zmieniło:

maxVisitors
i
currentVisitors
nadal są hoistowane, bo hoistingowi podlega każda deklaracja. Zmieniła się reakcja na pomyłkę - temporal dead zone przerabia zbyt wczesny odczyt na głośny błąd zamiast na dyskretne
undefined
. Każda wartość powstaje dokładnie tam, gdzie jest potrzebna, i ani chwili wcześniej, dzięki czemu logikę bramek czyta się z góry na dół, bez trzymania reguł hoistingu w głowie. O to właśnie chodzi w praktyce: nie o wykucie mechanizmu, tylko o pisanie kodu, w którym ten mechanizm nigdy nie zaskakuje.

Praktyczne wskazówki dotyczące hoistingu

  1. Zawsze deklaruj zmienne na górze ich zasięgu - to minimalizuje problemy związane z hoistingiem i sprawia, że kod czyta się dużo łatwiej.

  2. Preferuj

    let
    i
    const
    zamiast
    var
    - błędy z temporal dead zone są nieporównanie łatwiejsze do zdiagnozowania niż tajemnicze wartości
    undefined
    , które ujawniają się kilkaset linii dalej.

  3. Deklaruj funkcje przed ich użyciem - hoisting funkcji działa, ale naturalna kolejność czytania i tak pozostaje najczytelniejsza dla osoby, która wróci do tego kodu za pół roku.

  4. Unikaj deklaracji funkcji wewnątrz bloków warunkowych - zachowanie potrafi się różnić między środowiskami, więc bezpieczniej użyć wtedy wyrażenia funkcyjnego przypisanego do zmiennej.

  5. Pamiętaj, że hoisting dotyczy deklaracji, a nie inicjalizacji - przypisanie wartości zachodzi dokładnie tam, gdzie je napisaliśmy, nigdy wcześniej.

Większy przykład: system zarządzania incydentami w parku

Poniżej znajdziesz pełniejszy system zarządzania incydentami w Parku Jurajskim, który zbiera w jednym miejscu wszystko z tej lekcji: hoistowane deklaracje funkcji, zmienną

var
na poziomie modułu oraz
let
i
const
użyte do stanu, który się zmienia, i do stanu, który zmieniać się nie może.

1// System zarzadzania incydentami w Parku Jurajskim
2
3// Wykorzystanie hoistingu - deklarujemy zmienne na poczatku
4var activeIncidents = [];
5let alertLevel = "Normalny";
6const MAX_INCIDENTS = 5;
7
8// Glowna funkcja do inicjalizacji systemu
9function initIncidentSystem() {
10  // Ta zmienna jest hoistowana tylko w obrebie tej funkcji
11  var systemStatus = "Inicjalizacja";
12
13  // Wykorzystujemy hoisting funkcji - mozemy je wywolac przed definicja
14  logSystemStatus();
15  resetIncidents();
16
17  systemStatus = "Online";
18  logSystemStatus();
19
20  // Funkcja lokalna - widoczna tylko wewnatrz initIncidentSystem
21  function logSystemStatus() {
22    console.log(`Status systemu: ${systemStatus}`);
23  }
24
25  return {
26    reportIncident,      // Referencja do funkcji zdefiniowanej ponizej
27    checkAlertLevel,     // Funkcja zdefiniowana ponizej
28    getActiveIncidents,  // Funkcja zdefiniowana ponizej
29    resetIncidents       // Funkcja zdefiniowana wewnatrz initIncidentSystem
30  };
31
32  // Funkcja lokalna, ktora zwracamy jako czesc API
33  function resetIncidents() {
34    console.log("Resetowanie listy incydentow...");
35    activeIncidents = [];
36    updateAlertLevel();
37  }
38}
39
40// Deklaracje funkcji sa hoistowane, wiec mozna je wywolac przed definicja
41// Funkcja do zglaszania nowego incydentu
42function reportIncident(location, type, severity) {
43  console.log(`Zgloszenie incydentu: ${type} w lokalizacji ${location} (poziom zagrozenia: ${severity})`);
44
45  const incident = {
46    id: generateIncidentId(),
47    location,
48    type,
49    severity,
50    timestamp: new Date().toISOString(),
51    status: "Aktywny"
52  };
53
54  activeIncidents.push(incident);
55
56  if (activeIncidents.length > MAX_INCIDENTS) {
57    declareEmergency();
58  } else {
59    updateAlertLevel();
60  }
61
62  return incident;
63}
64
65// Generowanie unikalnego ID incydentu
66function generateIncidentId() {
67  return `INC-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
68}
69
70// Aktualizacja poziomu alertu na podstawie aktywnych incydentow
71function updateAlertLevel() {
72  const highSeverityCount = activeIncidents.filter(inc => inc.severity === "Wysoki").length;
73
74  if (highSeverityCount >= 3) {
75    alertLevel = "Krytyczny";
76  } else if (highSeverityCount > 0 || activeIncidents.length >= 3) {
77    alertLevel = "Podwyzszony";
78  } else if (activeIncidents.length > 0) {
79    alertLevel = "Ostroznosc";
80  } else {
81    alertLevel = "Normalny";
82  }
83
84  console.log(`Poziom alertu zaktualizowany: ${alertLevel}`);
85}
86
87// Funkcja wywolywana w przypadku zbyt wielu incydentow
88function declareEmergency() {
89  alertLevel = "Ewakuacja";
90  console.log("UWAGA! Zbyt wiele aktywnych incydentow. Oglaszamy ewakuacje parku!");
91  // Kod do inicjacji protokolow ewakuacji...
92}
93
94// Funkcja do sprawdzania aktualnego poziomu alertu
95function checkAlertLevel() {
96  return {
97    level: alertLevel,
98    incidentCount: activeIncidents.length,
99    timestamp: new Date().toISOString()
100  };
101}
102
103// Funkcja zwracajaca liste aktywnych incydentow
104function getActiveIncidents() {
105  return [...activeIncidents]; // Zwracamy kopie, aby nikt nie zmodyfikowal oryginalu
106}
107
108// Inicjalizacja systemu
109const incidentSystem = initIncidentSystem();
110
111// Testowanie systemu
112console.log("===== TESTOWANIE SYSTEMU ZARZADZANIA INCYDENTAMI =====");
113
114// Sprawdzanie poczatkowego stanu
115console.log("Poczatkowy poziom alertu:", incidentSystem.checkAlertLevel().level);
116
117// Zglaszanie incydentow
118incidentSystem.reportIncident("Zagroda T-Rex", "Uszkodzenie ogrodzenia", "Wysoki");
119incidentSystem.reportIncident("Centrum Gosci", "Brak zasilania", "Sredni");
120
121// Sprawdzanie stanu po zgloszeniach
122console.log("Aktywne incydenty:", incidentSystem.getActiveIncidents().length);
123console.log("Aktualny poziom alertu:", incidentSystem.checkAlertLevel().level);
124
125// Dodawanie kolejnych incydentow wysokiego priorytetu
126incidentSystem.reportIncident("Laboratorium", "Wyciek materialu genetycznego", "Wysoki");
127incidentSystem.reportIncident("Zagroda Velociraptorow", "Proba ucieczki", "Wysoki");
128
129// Powinno wywolac stan ewakuacji
130incidentSystem.reportIncident("Sektor B", "Dinozaur poza zagroda", "Wysoki");
131incidentSystem.reportIncident("Centrum Operacyjne", "Awaria systemow bezpieczenstwa", "Wysoki");
132
133// Reset systemu
134incidentSystem.resetIncidents();
135console.log("Po resecie - poziom alertu:", incidentSystem.checkAlertLevel().level);

Prześledź kolejność zdarzeń w tym pliku.

initIncidentSystem
wywołuje
logSystemStatus
i
resetIncidents
, zanim którakolwiek z nich pojawi się w kodzie, a
resetIncidents
stoi nawet za instrukcją
return
- mimo to wszystko działa, bo deklaracje funkcji są kompletne przed wykonaniem pierwszej linii ciała. Ze zmiennymi jest inaczej:
var systemStatus
można odczytać od samego początku funkcji, ale wartość
"Inicjalizacja"
dostaje dopiero w swojej linii, a
activeIncidents
,
alertLevel
i
MAX_INCIDENTS
celowo trafiły na samą górę pliku, żeby nikt nie sięgnął po nie za wcześnie.

Podsumowanie

Hoisting to ważny koncept JavaScriptu, który kształtuje sposób wykonywania naszego kodu:

  1. Deklaracje funkcji są hoistowane w całości - można ich używać przed deklaracją.

  2. Zmienne var są hoistowane i inicjalizowane wartością

    undefined
    - można się do nich odwołać wcześniej, ale do momentu przypisania nie niosą żadnej użytecznej wartości.

  3. Zmienne let i const (a razem z nimi klasy) są hoistowane, ale zostają w temporal dead zone - próba odczytu przed deklaracją kończy się błędem.

  4. Wyrażenia funkcyjne, w tym funkcje strzałkowe, podlegają regułom zmiennych - zmienna jest hoistowana, przypisana funkcja już nie.

Zrozumienie hoistingu jest kluczowe dla pisania przewidywalnego kodu JavaScript. Działa to dokładnie jak w samym Parku Jurajskim - znajomość zasad i protokołów wystarcza, żeby dzień minął bez niespodzianek i niebezpiecznych sytuacji.

"Hoisting w JavaScripcie przypomina poranne rozruchy parku" - mówi Dr. Rex. "Zanim przyjadą zwiedzający, zanim ruszy kod, część przygotowań dzieje się sama: protokoły są gotowe, procedury na miejscu. Ale prawdziwe dane trzeba dopiero zebrać na bieżąco, a sięganie po nie za wcześnie nie powie ci absolutnie nic!"

W kolejnej lekcji weźmiemy na warsztat słowo kluczowe

this
- kolejny fundamentalny, a przy tym potrafiący nieźle namieszać element JavaScriptu.

Przejdź do CodeWorlds