Closure (domknięcie) to funkcja, która pamięta zmienne z zakresu, w którym powstała — i ma do nich dostęp nawet wtedy, gdy ten zakres już dawno się zakończył. Innymi słowy: funkcja zabiera ze sobą „plecak” ze zmiennymi z otoczenia i nosi go tak długo, jak długo sama istnieje. Dzięki temu możesz schować dane przed światem zewnętrznym i stworzyć prywatny stan, do którego nikt nie dobierze się bez Twojej zgody.
Jak to działa
Kiedy funkcja jest definiowana wewnątrz innej funkcji, ma dostęp do trzech rzeczy: własnych zmiennych, zmiennych funkcji nadrzędnej oraz zmiennych globalnych. Normalnie po zakończeniu funkcji zewnętrznej jej zmienne lokalne znikają, bo garbage collector je sprząta. Ale jeśli funkcja wewnętrzna nadal się do nich odwołuje, silnik języka trzyma je przy życiu. To właśnie jest domknięcie — żywe powiązanie między funkcją a jej zakresem leksykalnym.
W praktyce closures są fundamentem wielu wzorców: callbacków, funkcji wyższego rzędu, modułów, memoizacji i tzw. fabryk funkcji. Pojawiają się w JavaScript, Pythonie, Swift, Rust, Go i wielu innych językach. Najczęściej kojarzy się je z JS, bo tam są wszechobecne.
Przykład z praktyki
Klasyczny licznik z prywatnym stanem w JavaScript. Zmienna count nie istnieje nigdzie indziej — nie dasz rady jej nadpisać z zewnątrz:
function makeCounter() { let count = 0; return () => ++count; }const next = makeCounter();next(); // 1a potemnext(); // 2
Każde wywołanie makeCounter() tworzy osobne domknięcie z własnym count. To dokładnie tak działają hooki w Reakcie (useState opiera się na closures) albo middleware w Express, gdzie konfigurujesz funkcję raz, a potem ona pamięta swoje ustawienia.
Częste błędy i pułapki
Najsłynniejsza wpadka to pętla z var i callbackiem: wszystkie domknięcia łapią tę samą zmienną, więc po pętli pokazują jej końcową wartość, a nie tę z danej iteracji. Lekarstwo: użyj let (ma zakres blokowy) albo przekaż wartość przez parametr.
Drugi temat to wycieki pamięci — domknięcie trzyma referencje, więc jeśli przypadkiem zamkniesz w nim ciężki obiekt (np. duży element DOM) i nie zwolnisz tej funkcji, pamięć zostanie zajęta. Closure to nie magia: dopóki funkcja żyje, jej plecak też.
Pojęcia powiązane: zakres leksykalny (lexical scope), funkcje wyższego rzędu, callback, hoisting, garbage collection, IIFE oraz wzorzec modułu.