Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Zasięg zmiennych (globalny, funkcyjny, blokowy)

Witaj ponownie w Parku Jurajskim! Dzisiaj zajmiemy się jednym z fundamentalnych konceptów JavaScript - zasięgiem zmiennych. Zrozumienie zasięgu zmiennych jest kluczowe dla pisania wydajnego, bezpiecznego i przewidywalnego kodu, szczególnie w złożonych systemach, takich jak nasz park pełen dinozaurów.

Czym jest zasięg zmiennych?

Zasięg zmiennych (scope) określa, gdzie w kodzie dana zmienna jest dostępna - innymi słowy, z którego miejsca w kodzie możemy się do niej odwołać. Podobnie jak w Parku Jurajskim mamy strefy z różnymi poziomami dostępu (strefa dla zwiedzających, laboratorium tylko dla naukowców, zagrody wymagające specjalnych uprawnień), tak w JavaScript mamy różne zasięgi dostępu do zmiennych.

W JavaScript wyróżniamy trzy główne typy zasięgów:

  1. Zasięg globalny (Global scope)
  2. Zasięg funkcyjny (Function scope)
  3. Zasięg blokowy (Block scope)

Zasięg globalny

Zmienne zdefiniowane poza jakąkolwiek funkcją lub blokiem kodu mają zasięg globalny. Oznacza to, że są dostępne w całym programie, w każdej funkcji i bloku kodu.

1// Zmienne globalne - dostępne wszędzie, jak publiczne informacje o parku
2const parkName = "Jurassic Park";
3let visitorCount = 0;
4var isOpenToPublic = true;
5
6function checkParkStatus() {
7  console.log(`Nazwa parku: ${parkName}`);
8  console.log(`Liczba odwiedzających: ${visitorCount}`);
9  console.log(`Otwarty dla zwiedzających: ${isOpenToPublic}`);
10}
11
12function updateVisitorCount(newCount) {
13  // Możemy modyfikować zmienną globalną z dowolnej funkcji
14  visitorCount = newCount;
15  console.log(`Zaktualizowano liczbę odwiedzających: ${visitorCount}`);
16}
17
18// Wywołanie funkcji korzystających z globalnych zmiennych
19checkParkStatus();
20updateVisitorCount(250);
21checkParkStatus();

Należy być ostrożnym ze zmiennymi globalnymi, ponieważ:

  • Mogą być przypadkowo zmodyfikowane z dowolnego miejsca w kodzie
  • Mogą prowadzić do konfliktów nazw
  • Utrudniają śledzenie stanu aplikacji
  • Zwiększają powiązania między różnymi częściami kodu

Zmienne globalne w naszym parku to jak dokumenty dostępne dla wszystkich pracowników - czasami są niezbędne, ale zbyt wiele takich dokumentów może prowadzić do bałaganu i nieporozumień.

Zasięg funkcyjny

Zmienne zdefiniowane wewnątrz funkcji są dostępne tylko wewnątrz tej funkcji. Są to zmienne lokalne dla danej funkcji.

1function monitorDinosaur(dinoId) {
2  // Te zmienne mają zasięg funkcyjny - są dostępne tylko wewnątrz tej funkcji
3  const startTime = Date.now();
4  let status = "Monitorowanie...";
5  var sensorData = [];
6  
7  // Możemy używać zmiennych wewnątrz funkcji
8  status = "Zbieranie danych...";
9  sensorData.push({ time: startTime, temperature: 37.5 });
10  
11  console.log(`Status: ${status}`);
12  console.log(`Dane z czujników: ${JSON.stringify(sensorData)}`);
13  
14  // Zwracamy wynik
15  return {
16    id: dinoId,
17    monitoringStarted: startTime,
18    lastStatus: status
19  };
20}
21
22// Wywołanie funkcji
23const monitoringResults = monitorDinosaur("T-Rex-01");
24
25// Poniższy kod spowodowałby błąd, ponieważ próbujemy uzyskać dostęp
26// do zmiennych o zasięgu funkcyjnym z zewnątrz funkcji
27// console.log(startTime);    // ReferenceError
28// console.log(status);       // ReferenceError
29// console.log(sensorData);   // ReferenceError
30
31// Ale mamy dostęp do wartości zwracanej przez funkcję
32console.log(monitoringResults);

Zasięg funkcyjny działa jak strefy zastrzeżone w parku - tylko upoważniony personel (kod wewnątrz funkcji) ma dostęp do informacji i zasobów w tej strefie.

Różnica między var, let i const w kontekście zasięgu funkcyjnego

Wszystkie trzy słowa kluczowe (

var
,
let
,
const
) tworzą zmienne o zasięgu funkcyjnym, ale jest między nimi istotna różnica:

  • var
    ma zasięg funkcyjny, ale nie blokowy
  • let
    i
    const
    mają zarówno zasięg funkcyjny jak i blokowy

Zobaczmy to na przykładzie:

1function securityCheck() {
2  // Wszystkie te zmienne są dostępne wewnątrz całej funkcji
3  var securityCode = "A1B2C3";
4  let accessLevel = 5;
5  const securityChief = "Robert Muldoon";
6  
7  if (accessLevel >= 5) {
8    var emergencyProtocol = "Code Red"; // zasięg funkcyjny!
9    let backupSystem = "Operational";   // zasięg blokowy
10    const safeRoom = "Bunker B";        // zasięg blokowy
11    
12    console.log(securityCode);    // A1B2C3 - dostępne
13    console.log(accessLevel);     // 5 - dostępne
14    console.log(securityChief);   // Robert Muldoon - dostępne
15  }
16  
17  console.log(emergencyProtocol); // Code Red - dostępne mimo że zdefiniowane w bloku if
18  // console.log(backupSystem);   // ReferenceError - zmienna ma zasięg blokowy
19  // console.log(safeRoom);       // ReferenceError - zmienna ma zasięg blokowy
20}
21
22securityCheck();

Zasięg blokowy

Zasięg blokowy został wprowadzony w ES6 (ES2015) wraz z nowymi słowami kluczowymi

let
i
const
. Zmienne zdefiniowane za pomocą
let
i
const
wewnątrz bloku kodu (ograniczonego nawiasami klamrowymi
{}
) są dostępne tylko wewnątrz tego bloku.

1function monitorDinosaurBehavior() {
2  let dangerLevel = 0;
3  
4  // Blok if - tworzy nowy zasięg
5  if (true) {
6    // Zasięg blokowy - zmienne dostępne tylko wewnątrz tego bloku
7    let behavior = "Spokojne";
8    const vitals = { heartRate: 80, temperature: 38 };
9    
10    // Modyfikacja zmiennej z zewnętrznego zasięgu
11    dangerLevel = behavior === "Agresywne" ? 5 : 1;
12    
13    console.log(behavior);     // Spokojne - dostępne
14    console.log(vitals);       // { heartRate: 80, temperature: 38 } - dostępne
15    console.log(dangerLevel);  // 1 - dostępne
16  }
17  
18  console.log(dangerLevel);    // 1 - dostępne bo zdefiniowane w szerszym zasięgu
19  // console.log(behavior);    // ReferenceError - zmienna ma zasięg blokowy
20  // console.log(vitals);      // ReferenceError - zmienna ma zasięg blokowy
21  
22  // Blok for - również tworzy nowy zasięg
23  for (let i = 0; i < 5; i++) {
24    // 'i' ma zasięg blokowy, dostępne tylko wewnątrz pętli
25    const sensorId = `Sensor-${i}`;
26    console.log(sensorId);
27  }
28  
29  // console.log(i);        // ReferenceError - 'i' ma zasięg blokowy
30  // console.log(sensorId); // ReferenceError - zmienna ma zasięg blokowy
31}
32
33monitorDinosaurBehavior();

Zasięg blokowy jest jak poszczególne zagrody w naszym parku - każda zagroda ma własne zasady i dostęp, a wyposażenie przypisane do jednej zagrody nie jest dostępne w innych zagrodach.

Różnica między var, let i const

Teraz możemy lepiej zrozumieć różnice między tymi trzema słowami kluczowymi:

  1. var
    :

    • Ma zasięg funkcyjny lub globalny, ale nie blokowy
    • Może być redeklarowana wielokrotnie
    • Może być modyfikowana
    • Jest hoistowana (wynoszona) z przypisaniem wartości
      undefined
  2. let
    :

    • Ma zasięg blokowy
    • Nie może być redeklarowana w tym samym zasięgu
    • Może być modyfikowana
    • Jest hoistowana bez przypisania wartości (temporal dead zone)
  3. const
    :

    • Ma zasięg blokowy
    • Nie może być redeklarowana w tym samym zasięgu
    • Nie może być modyfikowana (ale właściwości obiektów mogą być modyfikowane)
    • Jest hoistowana bez przypisania wartości (temporal dead zone)
1function demonstrateVariables() {
2  // var
3  var dinosaur = "T-Rex";
4  var dinosaur = "Velociraptor"; // OK - można redeklarować
5  dinosaur = "Triceratops";      // OK - można modyfikować
6  
7  // let
8  let park = "Jurassic Park";
9  // let park = "Dino World";    // Błąd - nie można redeklarować
10  park = "Jurassic World";       // OK - można modyfikować
11  
12  // const
13  const island = "Isla Nublar";
14  // const island = "Isla Sorna"; // Błąd - nie można redeklarować
15  // island = "Another Island";   // Błąd - nie można modyfikować
16  
17  // Ale właściwości obiektów const mogą być modyfikowane
18  const parkSystem = {
19    mainPower: true,
20    backupGenerator: false
21  };
22  
23  // To jest dozwolone
24  parkSystem.mainPower = false;
25  parkSystem.backupGenerator = true;
26  
27  console.log(parkSystem); // { mainPower: false, backupGenerator: true }
28}
29
30demonstrateVariables();

Zasięg leksykalny i domknięcia (closures)

JavaScript wykorzystuje zasięg leksykalny, co oznacza, że funkcje mogą uzyskać dostęp do zmiennych zdefiniowanych w ich zewnętrznym zakresie.

1function initSecuritySystem() {
2  // Zmienne w zasięgu funkcji zewnętrznej
3  const systemName = "JurassicSafe";
4  let isActive = false;
5  let alertLevel = 0;
6  
7  // Funkcja wewnętrzna ma dostęp do zmiennych z funkcji zewnętrznej
8  function activateSystem() {
9    isActive = true;
10    console.log(`System ${systemName} został aktywowany.`);
11    return true;
12  }
13  
14  // Jeszcze jedna funkcja wewnętrzna
15  function setAlertLevel(level) {
16    alertLevel = level;
17    console.log(`System ${systemName}: poziom alertu ustawiony na ${alertLevel}`);
18  }
19  
20  // Zwracamy funkcje wewnętrzne
21  return {
22    activate: activateSystem,
23    setAlert: setAlertLevel,
24    getStatus: function() {
25      return {
26        name: systemName,
27        active: isActive,
28        alertLevel: alertLevel
29      };
30    }
31  };
32}
33
34// Tworzymy instancję systemu bezpieczeństwa
35const securitySystem = initSecuritySystem();
36
37// Używamy zwróconych funkcji
38securitySystem.activate();           // System JurassicSafe został aktywowany.
39securitySystem.setAlert(3);          // System JurassicSafe: poziom alertu ustawiony na 3
40console.log(securitySystem.getStatus()); // { name: "JurassicSafe", active: true, alertLevel: 3 }

W powyższym przykładzie funkcje wewnętrzne tworzą domknięcie (closure) nad zmiennymi z funkcji

initSecuritySystem
. Nawet po zakończeniu wykonania
initSecuritySystem
, zwrócone funkcje nadal mają dostęp do tych zmiennych.

Domknięcia są jak zespoły bezpieczeństwa w parku - nawet gdy opuszczą centrum dowodzenia, nadal mają dostęp do kodów i protokołów, które zabrali ze sobą.

Praktyczny przykład: System zarządzania parkiem

Stwórzmy bardziej kompleksowy przykład, który demonstruje różne zasięgi zmiennych w kontekście systemu zarządzania parkiem:

1// Zmienne globalne - dostępne w całym programie
2const PARK_NAME = "Jurassic Park";
3let parkStatus = "closed";
4var totalVisitors = 0;
5
6// Funkcja IIFE (Immediately Invoked Function Expression) tworząca moduł
7const parkSystem = (function() {
8  // Zmienne prywatne - zasięg funkcyjny, niedostępne spoza modułu
9  let securityStatus = "active";
10  let powerStatus = "online";
11  const MAX_CAPACITY = 2000;
12  
13  // Prywatna funkcja pomocnicza
14  function checkSystemStatus() {
15    return powerStatus === "online" && securityStatus === "active";
16  }
17  
18  // Publiczne API modułu
19  return {
20    // Metoda otwierająca park dla zwiedzających
21    openPark: function() {
22      if (checkSystemStatus()) {
23        parkStatus = "open"; // Modyfikacja zmiennej globalnej
24        console.log(`${PARK_NAME} jest teraz otwarty dla zwiedzających!`);
25        return true;
26      } else {
27        console.log("Nie można otworzyć parku - systemy bezpieczeństwa nie są w pełni operacyjne.");
28        return false;
29      }
30    },
31    
32    // Metoda rejestrująca nowych odwiedzających
33    registerVisitors: function(count) {
34      // Sprawdzenie pojemności parku
35      if (parkStatus !== "open") {
36        console.log("Park jest zamknięty. Nie można zarejestrować odwiedzających.");
37        return false;
38      }
39      
40      // Zmienna z zasięgiem blokowym
41      const availableCapacity = MAX_CAPACITY - totalVisitors;
42      
43      if (count <= availableCapacity) {
44        totalVisitors += count; // Modyfikacja zmiennej globalnej
45        console.log(`Zarejestrowano ${count} nowych odwiedzających. Łącznie: ${totalVisitors}`);
46        
47        // Zasięg blokowy wewnątrz warunku
48        if (totalVisitors > MAX_CAPACITY * 0.8) {
49          const warningMessage = "Uwaga: Park zbliża się do maksymalnej pojemności!";
50          console.log(warningMessage);
51        }
52        
53        return true;
54      } else {
55        console.log(`Błąd: Za dużo odwiedzających. Dostępna pojemność: ${availableCapacity}`);
56        return false;
57      }
58    },
59    
60    // Metoda zamykająca park
61    closePark: function() {
62      // Funkcja lokalna z dostępem do zmiennych z szerszego zakresu
63      function evacuateVisitors() {
64        console.log(`Ewakuowanie ${totalVisitors} odwiedzających z parku...`);
65        totalVisitors = 0;
66      }
67      
68      parkStatus = "closed";
69      evacuateVisitors();
70      console.log(`${PARK_NAME} został zamknięty.`);
71      
72      return true;
73    },
74    
75    // Metoda zmieniająca status systemów
76    updateSystems: function(power, security) {
77      // Dla demonstracji let vs var
78      if (power) {
79        let localMessage = "Aktualizacja systemu zasilania...";
80        console.log(localMessage);
81        powerStatus = power;
82      }
83      
84      if (security) {
85        var statusMessage = "Aktualizacja systemu bezpieczeństwa...";
86        console.log(statusMessage);
87        securityStatus = security;
88      }
89      
90      // console.log(localMessage); // Błąd: localMessage ma zasięg blokowy
91      console.log(statusMessage);  // Działa: statusMessage ma zasięg funkcyjny
92      
93      return {
94        power: powerStatus,
95        security: securityStatus
96      };
97    }
98  };
99})();
100
101// Korzystanie z naszego systemu
102console.log(`Status parku przed otwarciem: ${parkStatus}`);
103parkSystem.openPark();
104console.log(`Status parku po otwarciu: ${parkStatus}`);
105
106parkSystem.registerVisitors(500);
107parkSystem.registerVisitors(1200);  // Powinien pokazać ostrzeżenie o pojemności
108parkSystem.registerVisitors(400);   // Powinien pokazać błąd o przekroczeniu pojemności
109
110// Aktualizacja systemów
111parkSystem.updateSystems("offline", null);
112parkSystem.openPark(); // Powinien pokazać błąd - nie można otworzyć parku
113
114// Przywrócenie systemów i zamknięcie parku
115parkSystem.updateSystems("online", "active");
116parkSystem.closePark();
117console.log(`Status parku po zamknięciu: ${parkStatus}`);
118console.log(`Liczba odwiedzających po ewakuacji: ${totalVisitors}`);
119
120// Próba dostępu do zmiennych prywatnych
121// console.log(securityStatus); // Błąd: zmienna niedostępna
122// console.log(MAX_CAPACITY);   // Błąd: zmienna niedostępna

Zasady dobrej praktyki

  1. Unikaj zmiennych globalnych - podobnie jak w rzeczywistym parku, im więcej publicznych obszarów, tym trudniej je monitorować i zabezpieczyć.

  2. Preferuj

    const
    nad
    let
    , a
    let
    nad
    var
    - większość zmiennych nie powinna zmieniać swoich wartości, a jeśli już, to powinny mieć jak najmniejszy zasięg.

  3. Używaj zasięgu blokowego - ograniczaj widoczność zmiennych do najmniejszego możliwego zakresu.

  4. Stosuj wzorzec modułu - enkapsuluj zmienne i funkcje w modułach, udostępniając tylko niezbędne API.

  5. Bądź świadomy domknięć - domknięcia są potężnym narzędziem, ale mogą prowadzić do wycieków pamięci, jeśli są używane nieprawidłowo.

Podsumowanie

Zasięg zmiennych w JavaScript jest jak system stref dostępu w Parku Jurajskim:

  • Zasięg globalny - jak publiczne obszary parku, dostępne dla wszystkich
  • Zasięg funkcyjny - jak strefy zastrzeżone dla określonych pracowników
  • Zasięg blokowy - jak poszczególne zagrody i laboratoria z własnym systemem dostępu

Zrozumienie zasięgu zmiennych pozwala nam pisać lepiej zorganizowany, bardziej bezpieczny i przewidywalny kod - podobnie jak dobre zarządzanie strefami w parku pełnym niebezpiecznych dinozaurów zwiększa bezpieczeństwo wszystkich.

W następnym module zagłębimy się w temat hoistingu (wynoszenia), który jest ściśle związany z zasięgiem zmiennych i ma istotny wpływ na sposób działania kodu JavaScript.

Ir a CodeWorlds