Zamknięcia w JavaScript są jednym z tych pojęć, które wielu z trudem mieści w głowie. W poniższym artykule wyjaśnię w jasny sposób, czym jest zamknięcie, i wyjaśnię to na prostych przykładach.
Zamknięcie jest cechą w JavaScript, gdzie funkcja wewnętrzna ma dostęp do zmiennych funkcji zewnętrznej (zamykającej) – łańcuch zakresu.
Zamknięcie ma trzy łańcuchy zakresów:
- ma dostęp do własnego zakresu – zmiennych zdefiniowanych pomiędzy nawiasami klamrowymi
- ma dostęp do zmiennych funkcji zewnętrznej
- ma dostęp do zmiennych globalnych
Dla niewtajemniczonych, ta definicja może wydawać się po prostu mnóstwem żargonu!
Ale czym tak naprawdę jest zamknięcie?
Przyjrzyjrzyjmy się prostemu przykładowi zamknięcia w JavaScript:
function outer() { var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
Mamy tutaj dwie funkcje:
- funkcję zewnętrzną
outer, która posiada zmiennąb, i zwraca funkcjęinnerfunction - an internal function
inner, która posiada zmiennąa, i uzyskuje dostęp do zmiennejouterb, w ramach swojego ciała funkcji
Zakres zmiennej b jest ograniczony do funkcji outer, a zakres zmiennej a jest ograniczony do funkcji inner.
Wywołajmy teraz funkcję outer(), a wynik działania funkcji outer() zapiszmy w zmiennej X. Następnie wywołajmy funkcję outer() po raz drugi i zapiszmy ją w zmiennej Y.
Zobaczmy krok po kroku, co się stanie, gdy funkcja outer() zostanie wywołana po raz pierwszy:
- Zmienna
bzostaje utworzona, jej zakres zostaje ograniczony do funkcjiouter(), a jej wartość zostaje ustawiona na10. - Kolejna linia to deklaracja funkcji, więc nic do wykonania.
- W ostatniej linii,
return innerszuka zmiennej o nazwieinner, stwierdza, że ta zmiennainnerjest w rzeczywistości funkcją, a więc zwraca całe ciało funkcjiinner. - Zawartość zwracana przez instrukcję return jest przechowywana w
X.
Tak więc,Xbędzie przechowywał następujące dane:function inner() {
var a=20;
console.log(a+b);
} - Funkcja
outer()kończy wykonywanie, a wszystkie zmienne w zakresieouter()teraz już nie istnieją.
Ta ostatnia część jest ważna do zrozumienia. Gdy funkcja zakończy swoje wykonywanie, wszelkie zmienne, które zostały zdefiniowane wewnątrz zakresu funkcji przestają istnieć.
Czas życia zmiennej zdefiniowanej wewnątrz funkcji jest czasem życia wykonania funkcji.
Co to oznacza, że w console.log(a+b), zmienna b istnieje tylko podczas wykonywania funkcji outer(). Gdy funkcja outer zakończy wykonywanie, zmienna b już nie istnieje.
Gdy funkcja jest wykonywana po raz drugi, zmienne funkcji są tworzone ponownie i żyją tylko do momentu zakończenia wykonywania funkcji.
Tak więc, gdy outer() jest wywoływany po raz drugi:
- Tworzona jest nowa zmienna
b, jej zakres jest ograniczony do funkcjiouter(), a jej wartość jest ustawiana na10. - Kolejna linia to deklaracja funkcji, więc nic do wykonania.
-
return innerzwraca całe ciało funkcjiinner. - Zawartość zwracana przez instrukcję return jest przechowywana w
Y. - Funkcja
outer()kończy wykonywanie, a wszystkie zmienne w zakresieouter()teraz już nie istnieją.
Ważnym punktem jest to, że kiedy funkcja outer() jest wywoływana po raz drugi, zmienna b jest tworzona na nowo. Ponadto, gdy funkcja outer() zakończy wykonywanie po raz drugi, ta nowa zmienna b ponownie przestaje istnieć.
To najważniejszy punkt, z którego należy zdać sobie sprawę. Zmienne wewnątrz funkcji powstają tylko wtedy, gdy funkcja jest uruchomiona, a przestają istnieć, gdy funkcja zakończy wykonywanie.
Powróćmy teraz do naszego przykładu kodu i spójrzmy na X i Y. Ponieważ funkcja outer() w momencie wykonania zwraca funkcję, zmienne X i Y są funkcjami.
Można to łatwo sprawdzić, dodając do kodu JavaScript następujące elementy:
console.log(typeof(X)); //X is of type function
console.log(typeof(Y)); //Y is of type function
Ponieważ zmienne X i Y są funkcjami, możemy je wykonać. W języku JavaScript funkcję można wykonać, dodając () po nazwie funkcji, np. X() i Y().
Kiedy wykonujemy X() i Y(), zasadniczo wykonujemy funkcję inner .
Prześledźmy krok po kroku, co się dzieje, gdy X() jest wykonywany po raz pierwszy:
- Zmienna
ajest tworzona, a jej wartość jest ustawiana na20. - JavaScript próbuje teraz wykonać
a + b. Tutaj sprawy stają się interesujące. JavaScript wie, żeaistnieje, ponieważ właśnie go utworzył. Jednak zmiennabjuż nie istnieje. Ponieważbjest częścią funkcji zewnętrznej,bistniałby tylko podczas wykonywania funkcjiouter(). Ponieważ funkcjaouter()zakończyła wykonywanie na długo przed tym, jak wywołaliśmyX(), wszelkie zmienne znajdujące się w zakresie funkcjiouterprzestają istnieć, a zatem zmiennabjuż nie istnieje.
Jak JavaScript sobie z tym radzi?
Zamknięcia
Funkcja inner może uzyskać dostęp do zmiennych funkcji zamykającej dzięki zamknięciom w JavaScript. Innymi słowy, funkcja inner zachowuje łańcuch zakresu funkcji zamykającej w czasie, gdy funkcja zamykająca została wykonana, a zatem może uzyskać dostęp do zmiennych funkcji zamykającej.
W naszym przykładzie, funkcja inner zachowała wartość b=10, gdy funkcja outer() została wykonana, i nadal ją zachowuje (zamyka).
Teraz odwołuje się do swojego łańcucha zakresu i zauważa, że ma wartość zmiennej b w swoim łańcuchu zakresu, ponieważ zamknął wartość b w ramach zamknięcia w punkcie, w którym funkcja outer została wykonana.
Tak więc, JavaScript zna a=20 i b=10, i może obliczyć a+b.
Możesz to zweryfikować, dodając następującą linię kodu do powyższego przykładu:
Otwórz element Inspect w Google Chrome i przejdź do Konsoli. Możesz rozwinąć element, aby faktycznie zobaczyć element Closure (pokazany w przedostatniej linii poniżej). Zauważ, że wartość b=10 jest zachowana w elemencie Closure nawet po tym, jak funkcja outer() zakończy swoje działanie.

Powróćmy teraz do definicji domknięć, którą widzieliśmy na początku i zobaczmy, czy teraz ma ona więcej sensu.
Więc funkcja wewnętrzna ma trzy łańcuchy zakresu:
- dostęp do własnego zakresu – zmienna
a - dostęp do zmiennych funkcji
outer– zmiennab, którą zawiera - dostęp do dowolnych zmiennych globalnych, które mogą być zdefiniowane
Zamknięcia w akcji
Aby podkreślić znaczenie domknięć, rozszerzmy przykład o trzy linie kodu:
Kiedy uruchomisz ten kod, zobaczysz następujące wyjście w console.log:
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
Przeanalizujmy ten kod krok po kroku, aby zobaczyć co dokładnie się dzieje i zobaczyć domknięcia w akcji!
var X = outer(); // outer() invoked the first time
Funkcja outer() jest wywoływana po raz pierwszy. Wykonywane są następujące kroki:
- Tworzona jest zmienna
bi ustawiana na10Tworzona jest zmiennac, i ustawiona na100
Nazwijmy tob(first_time)ic(first_time)dla naszego własnego odniesienia. - Funkcja
innerjest zwracana i przypisywana doXW tym momencie, zmiennabjest dołączona do łańcucha zakresu funkcjiinnerjako domknięcie zb=10, ponieważinnerużywa zmiennejb. - Funkcja
outerkończy wykonywanie, a wszystkie jej zmienne przestają istnieć. Zmiennacjuż nie istnieje, chociaż zmiennabistnieje jako zamknięcie wewnątrzinner.
var Y= outer(); // outer() invoked the second time
- Zmienna
bjest tworzona od nowa i jest ustawiana na10Zmiennacjest tworzona od nowa.
Zauważ, że mimo iżouter()została wykonana raz, zanim zmiennebicprzestały istnieć, po zakończeniu wykonywania funkcji są one tworzone jako zupełnie nowe zmienne.
Nazwijmy jeb(second_time)ic(second_time)dla naszego własnego odniesienia. - Funkcja
innerzostaje zwrócona i przypisana doYW tym momencie zmiennabjest dołączona do łańcucha zakresu funkcjiinnerjako zamknięcie zb(second_time)=10, ponieważinnerużywa zmiennejb. - Funkcja
outerkończy wykonywanie, a wszystkie jej zmienne przestają istnieć.
Zmiennac(second_time)już nie istnieje, choć zmiennab(second_time)istnieje jako domknięcie wewnątrzinner.
Zobaczmy teraz, co się stanie, gdy poniższe linie kodu zostaną wykonane:
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third timeY(); // Y() invoked the first time
Gdy X() jest wywoływany po raz pierwszy,
- tworzona jest zmienna
a, i ustawiona na20 - wartość
a=20, wartośćbpochodzi z wartości zamknięcia.b(first_time), więcb=10 - zmienne
aibzwiększają się o1 -
X()kończy wykonywanie i wszystkie jego zmienne wewnętrzne – zmiennaa– przestają istnieć.
Jednakżeb(first_time)został zachowany jako domknięcie, więcb(first_time)nadal istnieje.
Gdy X() jest wywoływany po raz drugi,
- zmienna
ajest tworzona od nowa, i ustawiona na20Każda poprzednia wartość zmiennejajuż nie istnieje, ponieważ przestała istnieć, gdyX()zakończył wykonywanie za pierwszym razem. - wartość
a=20wartośćbpobierana jest z wartości zamknięcia –b(first_time)Zauważ również, że zwiększyliśmy wartośćbo1z poprzedniego wykonania, więcb=11 - zmienne
aibsą zwiększane o1ponownie -
X()kończy wykonywanie i wszystkie jego zmienne wewnętrzne – zmienna a – przestają istnieć
Jednakże,b(first_time)jest zachowany, ponieważ zamknięcie nadal istnieje.
Gdy X() jest wywoływany po raz trzeci,
- zmienna
ajest tworzona od nowa, i ustawiona na20Wcześniejsza wartość zmiennejajuż nie istnieje, ponieważ przestała istnieć, gdyX()zakończył wykonywanie za pierwszym razem. - wartość
a=20, wartośćbpochodzi z wartości zamknięcia –b(first_time)
Zauważ również, że w poprzednim wykonaniu zwiększyliśmy wartośćbo1, więcb=12 - zmienne
aibsą inkrementowane przez1ponownie -
X()kończy wykonywanie, a wszystkie jego zmienne wewnętrzne – zmiennaa– przestają istnieć
Jednakżeb(first_time)jest zachowana, gdyż domknięcie nadal istnieje
Gdy Y() jest wywoływane po raz pierwszy,
- zmienna
ajest tworzona od nowa i ustawiana na20 - wartość
a=20, wartośćbpochodzi z wartości zamknięcia –b(second_time), więcb=10 - zmienne
aibsą inkrementowane o1 -
Y()kończy wykonywanie, i wszystkie jego zmienne wewnętrzne – zmiennaa– przestają istnieć
Jednakżeb(second_time)został zachowany jako domknięcie, więcb(second_time)nadal istnieje.
Uwagi końcowe
Zamknięcia są jednym z tych subtelnych pojęć w JavaScript, które są trudne do uchwycenia na początku. Ale kiedy już je zrozumiesz, zdasz sobie sprawę, że nie mogło być inaczej.
Mam nadzieję, że te wyjaśnienia krok po kroku pomogły Ci naprawdę zrozumieć koncepcję domknięć w JavaScript!
Inne artykuły:
- Szybki przewodnik po funkcjach „samowywołujących się” w JavaScript
- Zrozumienie zakresu funkcji vs. zakresu bloku w JavaScript
- Jak używać obietnic w JavaScript
- Jak zbudować prostą animację Sprite w JavaScript