Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Closures i zakres leksykalny

W Parku Jurajskim bezpieczeństwo to priorytet numer jeden. Każdy wybieg ma swoje ogrodzenie, każde laboratorium - zamknięte drzwi z kodem dostępu. Podobnie działa zakres (scope) w JavaScript - to niewidzialne ogrodzenie, które kontroluje, które zmienne są dostępne w danym miejscu kodu. A closures (domknięcia) to jak dinozaur, który zapamiętuje kod PIN do swojego wybiegu, nawet gdy strażnik już dawno odszedł.

Zakres leksykalny (Lexical Scope)

JavaScript używa zakresu leksykalnego - oznacza to, że dostęp do zmiennych jest określany przez miejsce, w którym kod został napisany, a nie gdzie został wywołany. Wyobraź sobie to jak mapę Parku Jurajskiego: każdy wybieg (funkcja) widzi główną drogę (zakres globalny) i swoje własne terytorium, ale nie widzi wnętrza innych wybiegów.

1// Zakres globalny - główna droga Parku Jurajskiego
2const parkName = "Jurassic Park";
3
4function enclosureA() {
5  // Zakres funkcji enclosureA - Wybieg A
6  const dinosaurA = "Tyrannosaurus Rex";
7
8  console.log(parkName);    // "Jurassic Park" - widzi zakres globalny
9  console.log(dinosaurA);   // "Tyrannosaurus Rex" - widzi swój zakres
10
11  function innerFence() {
12    // Zakres zagnieżdżony - wewnętrzne ogrodzenie
13    const babyDino = "Baby Rex";
14
15    console.log(parkName);   // "Jurassic Park" - widzi zakres globalny
16    console.log(dinosaurA);  // "Tyrannosaurus Rex" - widzi zakres rodzica
17    console.log(babyDino);   // "Baby Rex" - widzi swój zakres
18  }
19
20  innerFence();
21  // console.log(babyDino); // BŁĄD! Nie widzi wnętrza innerFence
22}
23
24function enclosureB() {
25  // Zakres funkcji enclosureB - Wybieg B
26  const dinosaurB = "Triceratops";
27
28  console.log(parkName);     // "Jurassic Park" - widzi zakres globalny
29  // console.log(dinosaurA); // BŁĄD! Nie widzi wnętrza enclosureA
30}

Zasada jest prosta: funkcja wewnętrzna widzi zmienne z funkcji zewnętrznej, ale nie odwrotnie. Jak dinozaur w wewnętrznym wybiegu widzi przez ogrodzenie na zewnątrz, ale strażnik z zewnątrz nie widzi przez podwójne ściany do środka.

Czym jest Closure (Domknięcie)?

Closure to funkcja, która "pamięta" zmienne z zakresu, w którym została utworzona, nawet po tym, jak ten zakres przestał istnieć. To jak dinozaur, który zapamiętał drogę ucieczki - nawet gdy ogrodzenie zostanie przebudowane, on wciąż pamięta stary układ.

1function createDinoTracker(species) {
2  // Zmienna "species" jest w zakresie createDinoTracker
3  let sightings = 0;
4
5  // Ta funkcja wewnętrzna tworzy CLOSURE
6  // - "pamięta" species i sightings, nawet po zakończeniu createDinoTracker
7  return function reportSighting(location) {
8    sightings++;
9    console.log(species + " widziany w: " + location + " (obserwacja #" + sightings + ")");
10  };
11}
12
13// createDinoTracker zakończyło działanie, ale zwrócona funkcja PAMIĘTA zmienne
14const trackRex = createDinoTracker("T-Rex");
15const trackRaptor = createDinoTracker("Velociraptor");
16
17trackRex("Sektor A");       // "T-Rex widziany w: Sektor A (obserwacja #1)"
18trackRex("Sektor B");       // "T-Rex widziany w: Sektor B (obserwacja #2)"
19trackRaptor("Laboratorium"); // "Velociraptor widziany w: Laboratorium (obserwacja #1)"
20
21// Każde closure ma WŁASNĄ kopię zmiennych!
22// trackRex ma swoje sightings = 2, trackRaptor ma swoje sightings = 1

Praktyczne zastosowania closures

Prywatność danych (enkapsulacja)

Closures pozwalają tworzyć prywatne zmienne - dane, do których nie można dostać się z zewnątrz. Jak system bezpieczeństwa parku, gdzie kody dostępu są ukryte wewnątrz zamkniętego systemu.

1function createSecuritySystem(enclosureName) {
2  // Te zmienne są PRYWATNE - niedostępne z zewnątrz
3  let accessCode = Math.floor(Math.random() * 9000) + 1000;
4  let isLocked = true;
5  const accessLog = [];
6
7  return {
8    unlock(code) {
9      if (code === accessCode) {
10        isLocked = false;
11        accessLog.push("Odblokowano: " + new Date().toLocaleTimeString());
12        console.log(enclosureName + " - ogrodzenie WYŁĄCZONE");
13      } else {
14        accessLog.push("Nieudana próba: " + new Date().toLocaleTimeString());
15        console.log(enclosureName + " - BŁĘDNY KOD!");
16      }
17    },
18    lock() {
19      isLocked = true;
20      accessLog.push("Zablokowano: " + new Date().toLocaleTimeString());
21      console.log(enclosureName + " - ogrodzenie WŁĄCZONE");
22    },
23    getStatus() {
24      return isLocked ? "ZAMKNIĘTY" : "OTWARTY";
25    },
26    getLog() {
27      return [...accessLog]; // Zwraca kopię, nie oryginał
28    }
29  };
30}
31
32const rexSecurity = createSecuritySystem("Wybieg T-Rex");
33// Nie ma sposobu, by bezpośrednio odczytać accessCode!
34// rexSecurity.accessCode → undefined (prywatna zmienna)

Fabryki funkcji (Function Factories)

Closures umożliwiają tworzenie fabryk - funkcji, które produkują inne funkcje z wbudowaną konfiguracją. Jak laboratorium genetyczne, które tworzy dinozaury z określonym DNA.

1function createDinoFilter(property, value) {
2  // Closure "zapamiętuje" property i value
3  return function(dinosaur) {
4    return dinosaur[property] === value;
5  };
6}
7
8const isCarnivore = createDinoFilter("diet", "carnivore");
9const isHerbivore = createDinoFilter("diet", "herbivore");
10const isSectorA = createDinoFilter("sector", "A");
11
12const dinosaurs = [
13  { name: "Rex", diet: "carnivore", sector: "A" },
14  { name: "Tricia", diet: "herbivore", sector: "B" },
15  { name: "Blue", diet: "carnivore", sector: "C" }
16];
17
18console.log(dinosaurs.filter(isCarnivore)); // Rex, Blue
19console.log(dinosaurs.filter(isHerbivore)); // Tricia
20console.log(dinosaurs.filter(isSectorA));   // Rex

IIFE - Immediately Invoked Function Expressions

IIFE (natychmiast wywoływane wyrażenie funkcyjne) to funkcja, która jest definiowana i od razu wykonywana. To jak jednorazowy system bezpieczeństwa - uruchamia się, wykonuje swoje zadanie i znika, nie zostawiając śladów w zakresie globalnym.

1// Składnia IIFE
2(function() {
3  const secretData = "Lokalizacja ukrytego laboratorium";
4  console.log("IIFE wykonane! Dane: " + secretData);
5})();
6// secretData nie istnieje w zakresie globalnym!
7
8// IIFE ze strzałką
9(() => {
10  const emergencyProtocol = "Ewakuacja sektora 7";
11  console.log("Protokół awaryjny aktywowany: " + emergencyProtocol);
12})();
13
14// IIFE z parametrami
15const dinoReport = (function(parkData) {
16  const processed = parkData.map(d => d.toUpperCase());
17  return processed.join(", ");
18})(["rex", "raptor", "triceratops"]);
19
20console.log(dinoReport); // "REX, RAPTOR, TRICERATOPS"

IIFE były bardzo popularne przed ES6, gdy

var
nie miał zakresu blokowego. Dziś nadal przydają się do izolacji kodu i tworzenia modułów.

Hoisting - var vs let/const

Hoisting (wynoszenie) to mechanizm JavaScript, w którym deklaracje zmiennych i funkcji są "przenoszone" na początek swojego zakresu. Ale zachowują się różnie w zależności od słowa kluczowego!

1// var - hoisting z wartością undefined
2console.log(dinoVar);    // undefined (nie błąd!)
3var dinoVar = "Stegosaurus";
4console.log(dinoVar);    // "Stegosaurus"
5
6// let/const - hoisting ale z "Temporal Dead Zone" (TDZ)
7// console.log(dinoLet);  // ReferenceError! Zmienna w TDZ
8let dinoLet = "Ankylosaurus";
9console.log(dinoLet);    // "Ankylosaurus"
10
11// const - tak samo jak let, plus wymaga natychmiastowej inicjalizacji
12// console.log(dinoConst); // ReferenceError! TDZ
13const dinoConst = "Brachiosaurus";

var vs let w pętlach - klasyczna pułapka

To jeden z najsłynniejszych problemów w JavaScript, gdzie closures i

var
tworzą nieoczekiwane rezultaty:

1// Problem z var - wszystkie closures dzielą tę samą zmienną
2for (var i = 0; i < 3; i++) {
3  setTimeout(function() {
4    console.log("var: Dinozaur #" + i); // Zawsze "Dinozaur #3"!
5  }, 100);
6}
7
8// Rozwiązanie z let - każda iteracja ma własną kopię
9for (let j = 0; j < 3; j++) {
10  setTimeout(function() {
11    console.log("let: Dinozaur #" + j); // 0, 1, 2 - poprawnie!
12  }, 100);
13}

Dlaczego

var
nie działa? Bo
var
ma zakres funkcyjny, więc wszystkie trzy closures odwołują się do tej samej zmiennej
i
, która po zakończeniu pętli ma wartość 3.
let
ma zakres blokowy - każda iteracja pętli tworzy nową zmienną
j
.

Podsumowanie

| Koncept | Analogia w Parku Jurajskim | |---------|---------------------------| | Zakres globalny | Główna droga parku - widoczna ze wszystkich wybiegów | | Zakres funkcji | Wybieg dinozaura - widzi na zewnątrz, ale nikt nie widzi do środka | | Closure | Dinozaur pamięta układ parku nawet po przebudowie ogrodzeń | | Prywatne zmienne | Kod PIN do ogrodzenia - ukryty w zamkniętym systemie | | IIFE | Jednorazowy protokół bezpieczeństwa - wykonaj i zapomnij | | Hoisting var | Dinozaur, który pojawia się na liście przed wykluczeniem z jaja | | TDZ (let/const) | Strefa zakazu wstępu do momentu oficjalnego otwarcia wybiegu |

Vai a CodeWorlds