Zadałem pytanie o curry i wspomniano o zamknięciach. Co to jest zamknięcie? Jak to się ma do curry?
432
Zadałem pytanie o curry i wspomniano o zamknięciach. Co to jest zamknięcie? Jak to się ma do curry?
Odpowiedzi:
Zmienny zakres
Gdy deklarujesz zmienną lokalną, ta zmienna ma zasięg. Zasadniczo zmienne lokalne istnieją tylko w bloku lub funkcji, w której je deklarujesz.
Jeśli spróbuję uzyskać dostęp do zmiennej lokalnej, większość języków będzie jej szukała w bieżącym zakresie, a następnie w zakresach nadrzędnych, aż dotrą do zakresu głównego.
Kiedy wykonywany jest blok lub funkcja, jej zmienne lokalne nie są już potrzebne i zwykle są usuwane z pamięci.
Tak zwykle oczekujemy, że wszystko zadziała.
Zamknięcie jest trwałym zakresem zmiennej lokalnej
Zamknięcie jest trwałym zakresem, który zachowuje zmienne lokalne, nawet po wykonaniu kodu z tego bloku. Języki obsługujące zamknięcie (takie jak JavaScript, Swift i Ruby) pozwolą zachować referencję do zakresu (w tym jego zakresów nadrzędnych), nawet po zakończeniu wykonywania bloku, w którym zadeklarowano zmienne, pod warunkiem, że zachowasz referencję do tego bloku lub funkcji gdzieś.
Obiekt zasięgu i wszystkie jego zmienne lokalne są powiązane z funkcją i będą istnieć tak długo, jak długo ta funkcja będzie się utrzymywać.
To daje nam przenośność funkcji. Możemy spodziewać się, że wszystkie zmienne, które były w zakresie, gdy funkcja została zdefiniowana po raz pierwszy, nadal będą w zakresie, gdy później wywołamy funkcję, nawet jeśli wywołasz funkcję w zupełnie innym kontekście.
Na przykład
Oto naprawdę prosty przykład w JavaScript, który ilustruje tę kwestię:
Tutaj zdefiniowałem funkcję w ramach funkcji. Funkcja wewnętrzna uzyskuje dostęp do wszystkich zmiennych lokalnych funkcji zewnętrznej, w tym
a
. Zmiennaa
wchodzi w zakres funkcji wewnętrznej.Zwykle po wyjściu funkcji wszystkie zmienne lokalne są zdmuchiwane. Jeśli jednak zwrócimy funkcję wewnętrzną i przypiszemy ją do zmiennej
fnc
, która będzie się utrzymywać poouter
jej wyjściu, wszystkie zmienne, które były w zasięgu, gdyinner
zostały zdefiniowane, również zostaną zachowane . Zmiennaa
została zamknięta - znajduje się w zamknięciu.Zauważ, że zmienna
a
jest całkowicie prywatnafnc
. Jest to sposób tworzenia prywatnych zmiennych w funkcjonalnym języku programowania, takim jak JavaScript.Jak możesz się domyślić, kiedy to nazywam
fnc()
, wypisuje wartośća
, która wynosi „1”.W języku bez zamknięcia zmienna
a
byłaby usuwana i wyrzucana po wyrzuceniu funkcjiouter
. Wywołanie fnc spowodowałoby błąd, ponieważa
już nie istnieje.W JavaScript zmienna jest
a
utrzymywana, ponieważ zakres zmiennej jest tworzony, gdy funkcja jest deklarowana po raz pierwszy i trwa tak długo, jak długo funkcja istnieje.a
należy do zakresuouter
. Zakresinner
ma wskaźnik nadrzędny do zakresuouter
.fnc
jest zmienną, która wskazujeinner
.a
trwa tak długo, jakfnc
trwa.a
jest w zamknięciu.źródło
Podam przykład (w JavaScript):
Funkcja ta, makeCounter, polega na tym, że zwraca funkcję, którą nazwaliśmy x, która będzie zliczać o jedną za każdym razem, gdy zostanie wywołana. Ponieważ nie podajemy żadnych parametrów x, musi jakoś zapamiętać liczbę. Wie, gdzie ją znaleźć na podstawie tak zwanego zakresu leksykalnego - musi znaleźć miejsce, w którym jest zdefiniowane, aby znaleźć wartość. Ta „ukryta” wartość nazywana jest zamknięciem.
Oto mój przykład curry:
Możesz zobaczyć, że kiedy wywołujesz add z parametrem a (czyli 3), ta wartość jest zawarta w zamknięciu zwróconej funkcji, którą definiujemy jako add3. W ten sposób, gdy wywołujemy add3, wie, gdzie znaleźć wartość do wykonania dodania.
źródło
Odpowiedź Kyle'a jest całkiem dobra. Myślę, że jedynym dodatkowym wyjaśnieniem jest to, że zamknięcie jest w zasadzie migawką stosu w punkcie, w którym tworzona jest funkcja lambda. Następnie, gdy funkcja jest ponownie wykonywana, stos jest przywracany do tego stanu przed wykonaniem funkcji. Zatem, jak wspomina Kyle, ta ukryta wartość (
count
) jest dostępna, gdy funkcja lambda jest wykonywana.źródło
Po pierwsze, w przeciwieństwie do tego, co mówi większość ludzi tutaj, zamknięcie nie jest funkcją ! Co to jest?
Jest to zestaw symboli zdefiniowanych w „otaczającym kontekście” funkcji (znanym jako jej środowisko ), które sprawiają, że jest to wyrażenie ZAMKNIĘTE (to znaczy wyrażenie, w którym każdy symbol jest zdefiniowany i ma wartość, dzięki czemu można go ocenić).
Na przykład, jeśli masz funkcję JavaScript:
jest to wyrażenie zamknięte, ponieważ wszystkie występujące w nim symbole są w nim zdefiniowane (ich znaczenie jest jasne), więc możesz je ocenić. Innymi słowy, jest samowystarczalny .
Ale jeśli masz taką funkcję:
jest to wyrażenie otwarte, ponieważ znajdują się w nim symbole, które nie zostały w nim zdefiniowane. Mianowicie
y
. Patrząc na tę funkcję, nie możemy powiedzieć, coy
jest i co to znaczy, nie znamy jej wartości, więc nie możemy ocenić tego wyrażenia. Tzn. Nie możemy wywołać tej funkcji, dopóki nie powiemy, coy
ma w niej oznaczać. Toy
się nazywa wolny zmienna .To
y
wymaga definicji, ale ta definicja nie jest częścią funkcji - jest zdefiniowana gdzie indziej, w jej „otaczającym kontekście” (znanym również jako środowisko ). Przynajmniej na to liczymy: PNa przykład można go zdefiniować globalnie:
Lub może być zdefiniowany w funkcji, która go otacza:
Część środowiska, która nadaje wolnym zmiennym w wyrażeniu swoje znaczenie, to zamknięcie . Nazywa się to w ten sposób, ponieważ zamienia wyrażenie otwarte w zamknięte , podając brakujące definicje dla wszystkich wolnych zmiennych , abyśmy mogli je ocenić.
W powyższym przykładzie funkcja wewnętrzna (której nie nadaliśmy nazwy, ponieważ jej nie potrzebowaliśmy) jest wyrażeniem otwartym, ponieważ zmienna
y
w niej jest wolna - jej definicja znajduje się poza funkcją, w funkcji, która ją otacza . Środowisko dla tej anonimowej funkcji jest zbiorem zmiennych:Teraz zamknięcie jest tą częścią tego środowiska, która zamyka funkcję wewnętrzną, podając definicje dla wszystkich wolnych zmiennych . W naszym przypadku jedyną wolną zmienną w funkcji wewnętrznej było
y
, więc zamknięciem tej funkcji jest ten podzbiór jej środowiska:Pozostałe dwa symbole zdefiniowane w środowisku nie są częścią zamknięcia tej funkcji, ponieważ nie wymaga ich uruchomienia. Nie są potrzebne do jego zamknięcia .
Więcej na temat teorii tutaj: https://stackoverflow.com/a/36878651/434562
Warto zauważyć, że w powyższym przykładzie funkcja otoki zwraca wartość wewnętrzną jako wartość. Moment, w którym wywołamy tę funkcję, może być zdalny w czasie od momentu jej zdefiniowania (lub utworzenia). W szczególności jego funkcja zawijania już nie działa, a jej parametrów, które były na stosie wywołań, już nie ma: P Stwarza to problem, ponieważ funkcja wewnętrzna musi
y
tam być, gdy jest wywoływana! Innymi słowy, wymaga zmiennych od swojego zamknięcia, aby jakoś przeżyć funkcję otoki i być tam, kiedy jest to potrzebne. Dlatego funkcja wewnętrzna musi wykonać migawkę tych zmiennych, które powodują jej zamknięcie i przechowywać je w bezpiecznym miejscu do późniejszego wykorzystania. (Gdzieś poza stosem wywołań.)I dlatego ludzie często mylą pojęcie zamknięcia jako specjalnego rodzaju funkcji, która może wykonywać takie migawki zmiennych zewnętrznych, których używają, lub struktury danych używanej do przechowywania tych zmiennych na później. Ale mam nadzieję, że rozumiecie teraz, że nie są one samym zamknięciem - to tylko sposoby implementacji zamknięć w języku programowania lub mechanizmach językowych, które pozwalają, aby zmienne z zamknięcia funkcji były tam, gdzie jest to potrzebne. Istnieje wiele nieporozumień dotyczących zamknięć, które (niepotrzebnie) sprawiają, że ten temat jest znacznie bardziej zagmatwany i skomplikowany, niż jest w rzeczywistości.
źródło
Zamknięcie jest funkcją, która może odnosić się do stanu w innej funkcji. Na przykład w Pythonie używa zamknięcia „wewnętrznego”:
źródło
Aby ułatwić zrozumienie zamknięć, przydatne może być zbadanie, w jaki sposób można je wdrożyć w języku proceduralnym. Wyjaśnienie to nastąpi po uproszczonej implementacji zamknięć w systemie.
Na początek muszę wprowadzić pojęcie przestrzeni nazw. Po wprowadzeniu polecenia do interpretera schematu musi on ocenić różne symbole w wyrażeniu i uzyskać ich wartość. Przykład:
Wyrażenia definicyjne przechowują wartość 3 w miejscu dla x i wartość 4 w miejscu dla y. Następnie, gdy wywołujemy (+ xy), interpreter sprawdza wartości w przestrzeni nazw i jest w stanie wykonać operację i zwraca 7.
Jednak w schemacie istnieją wyrażenia, które pozwalają tymczasowo zastąpić wartość symbolu. Oto przykład:
To, co robi słowo kluczowe let, wprowadza nową przestrzeń nazw z x jako wartością 5. Zauważysz, że nadal jest w stanie zobaczyć, że y wynosi 4, dzięki czemu suma jest zwracana do 9. Możesz także zobaczyć, że po zakończeniu wyrażenia x powraca do bycia 3. W tym sensie x został tymczasowo zamaskowany przez wartość lokalną.
Języki proceduralne i obiektowe mają podobną koncepcję. Za każdym razem, gdy deklarujesz zmienną w funkcji o tej samej nazwie co zmienna globalna, uzyskujesz ten sam efekt.
Jak moglibyśmy to wdrożyć? Prostą metodą jest lista połączona - głowa zawiera nową wartość, a ogon zawiera starą przestrzeń nazw. Kiedy musisz spojrzeć na symbol, zaczynasz od głowy i schodzisz po ogonie.
Przejdźmy teraz do implementacji pierwszorzędnych funkcji. Mniej więcej funkcja jest zestawem instrukcji do wykonania, gdy wywoływana jest funkcja, której wynikiem jest wartość zwracana. Kiedy czytamy funkcję, możemy przechowywać te instrukcje za scenami i uruchamiać je, gdy funkcja jest wywoływana.
Definiujemy x na 3, a plus-x jako jego parametr, y plus wartość x. Na koniec wywołujemy plus-x w środowisku, w którym x zostało zamaskowane przez nowy x, ten miał wartość 5. Jeśli przechowujemy jedynie operację (+ xy) dla funkcji plus-x, ponieważ jesteśmy w kontekście przy x równym 5, zwracanym wynikiem będzie 9. To jest tak zwane dynamiczne określanie zakresu.
Jednak Scheme, Common Lisp i wiele innych języków ma tak zwany zakres leksykalny - oprócz przechowywania operacji (+ xy) przechowujemy również przestrzeń nazw w tym konkretnym punkcie. W ten sposób, patrząc na wartości, widzimy, że x, w tym kontekście, to naprawdę 3. To jest zamknięcie.
Podsumowując, możemy użyć połączonej listy do przechowywania stanu przestrzeni nazw w momencie definicji funkcji, co pozwala nam na dostęp do zmiennych z obejmujących zakresy, a także daje nam możliwość lokalnego maskowania zmiennej bez wpływu na resztę program.
źródło
Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Oto prawdziwy przykład tego, dlaczego Closures kopie tyłek ... To jest prosto z mojego kodu JavaScript. Pozwól mi zilustrować.
A oto, jak byś tego użył:
Teraz wyobraź sobie, że chcesz rozpocząć odtwarzanie z opóźnieniem, na przykład 5 sekund później po uruchomieniu tego fragmentu kodu. Cóż, to jest łatwe
delay
i to jest zamknięcie:Gdy wywołujesz za
delay
pomocą5000
ms, pierwszy fragment jest uruchamiany i przechowuje przekazane argumenty w swoim zamknięciu. Następnie 5 sekund później, kiedysetTimeout
nastąpi wywołanie zwrotne, zamknięcie nadal zachowuje te zmienne, dzięki czemu może wywoływać funkcję oryginalną z oryginalnymi parametrami.Jest to rodzaj dekoracji curry lub funkcji.
Bez zamknięć musiałbyś w jakiś sposób utrzymać stan tych zmiennych poza funkcją, w ten sposób zaśmiecając kod poza funkcją z czymś, co logicznie należy do niej. Korzystanie z zamknięć może znacznie poprawić jakość i czytelność kodu.
źródło
Funkcje nie zawierające wolnych zmiennych nazywane są funkcjami czystymi.
Funkcje zawierające jedną lub więcej wolnych zmiennych nazywane są zamknięciami.
src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure
źródło
tl; dr
Zamknięcie jest funkcją, a jej zakres jest przypisany (lub użyty jako) zmienna. Zatem zamknięcie nazwy: zakres i funkcja są zamknięte i używane tak jak każdy inny byt.
Szczegółowe wyjaśnienie stylu Wikipedii
Według Wikipedii zamknięcie to:
Co to znaczy? Przyjrzyjmy się niektórym definicjom.
Wyjaśnię zamknięcia i inne powiązane definicje za pomocą tego przykładu:
Funkcje pierwszej klasy
Zasadniczo oznacza to, że możemy korzystać z funkcji tak jak z każdego innego bytu . Możemy je modyfikować, przekazywać jako argumenty, zwracać z funkcji lub przypisywać do zmiennych. Technicznie rzecz biorąc, są pierwszorzędnymi obywatelami , stąd nazwa: pierwszorzędne funkcje.
W powyższym przykładzie
startAt
zwraca ( anonimową ) funkcję, której funkcja zostanie przypisana doclosure1
iclosure2
. Tak więc, jak widzisz, JavaScript traktuje funkcje tak jak wszystkie inne podmioty (obywatele pierwszej klasy).Wiązanie nazwy
Powiązanie nazwy polega na ustaleniu, do jakich danych odwołuje się zmienna (identyfikator) . Zakres jest tutaj naprawdę ważny, ponieważ to on decyduje o sposobie rozwiązania powiązania.
W powyższym przykładzie:
y
jest związany3
.startAt
zakresiex
jest związany1
lub5
(w zależności od zamknięcia).Wewnątrz zakresu funkcji anonimowej
x
nie jest powiązana z żadną wartością, dlatego należy ją rozwiązać w górnymstartAt
zakresie.Zakres leksykalny
Jak mówi Wikipedia , zakres:
Istnieją dwie techniki:
Aby uzyskać więcej wyjaśnień, sprawdź to pytanie i zajrzyj na Wikipedię .
W powyższym przykładzie widzimy, że JavaScript ma zasięg leksykalny, ponieważ po
x
rozwiązaniu powiązanie jest przeszukiwane w górnymstartAt
zakresie, w oparciu o kod źródłowy (anonimowa funkcja szukająca x jest zdefiniowana wewnątrzstartAt
) i nie opiera się na stosie wywołań, sposobie (zasięgu gdzie) funkcja została wywołana.Zawijanie (zamykanie) w górę
W naszym przykładzie, gdy wywołamy
startAt
, zwróci funkcję (pierwszej klasy), do której zostanie przypisane,closure1
aclosure2
zatem zostanie utworzone zamknięcie, ponieważ przekazane zmienne1
i5
zostaną zapisane wstartAt
zakresie, który zostanie dołączony do zwróconego funkcja anonimowa. Kiedy wywołujemy tę anonimową funkcję za pomocąclosure1
i zaclosure2
pomocą tego samego argumentu (3
), wartośćy
zostanie znaleziona natychmiast (ponieważ jest to parametr tej funkcji), alex
nie jest ograniczona zakresem funkcji anonimowej, więc rozdzielczość jest kontynuowana w (leksykalnie) górny zakres funkcji (który został zapisany w zamknięciu), w którymx
stwierdzono, że jest związany z jednym1
lub drugim5
. Teraz wiemy wszystko o podsumowaniu, więc wynik można zwrócić, a następnie wydrukować.Teraz powinieneś zrozumieć zamknięcia i jak się zachowują, co jest podstawową częścią JavaScript.
Curry
Aha, a także nauczyłeś się, o co chodzi w curry : używasz funkcji (zamknięć) do przekazywania każdego argumentu operacji zamiast używania jednej funkcji z wieloma parametrami.
źródło
Zamknięcie to funkcja JavaScript, w której funkcja ma dostęp do własnych zmiennych zakresu, dostęp do zewnętrznych zmiennych funkcji i dostęp do zmiennych globalnych.
Zamknięcie ma dostęp do swojego zakresu funkcji zewnętrznej nawet po powrocie funkcji zewnętrznej. Oznacza to, że zamknięcie może zapamiętać i uzyskać dostęp do zmiennych i argumentów swojej funkcji zewnętrznej, nawet po zakończeniu funkcji.
Funkcja wewnętrzna może uzyskać dostęp do zmiennych zdefiniowanych we własnym zakresie, zakresie funkcji zewnętrznej i zasięgu globalnym. A funkcja zewnętrzna może uzyskać dostęp do zmiennej zdefiniowanej we własnym zakresie i zakresie globalnym.
Przykład zamknięcia :
Wyjście będzie równe 20, która będzie sumą jego zmiennej własnej, wartości zmiennej zewnętrznej i wartości zmiennej globalnej.
źródło
W normalnej sytuacji zmienne są powiązane regułą określania zakresu: zmienne lokalne działają tylko w obrębie zdefiniowanej funkcji. Zamknięcie jest sposobem na tymczasowe złamanie tej zasady dla wygody.
w powyższym kodzie
lambda(|n| a_thing * n}
jest zamknięcie, ponieważa_thing
odwołuje się do niego lambda (anonimowy twórca funkcji).Teraz, jeśli umieścisz wynikową anonimową funkcję w zmiennej funkcji.
foo złamie normalną zasadę określania zakresu i zacznie używać 4 wewnętrznie.
zwraca 12.
źródło
Krótko mówiąc, wskaźnik funkcji jest tylko wskaźnikiem do lokalizacji w bazie kodu programu (jak licznik programu). Natomiast zamknięcie = wskaźnik funkcji + ramka stosu .
.
źródło
• Zamknięcie jest podprogramem i środowiskiem odniesienia, w którym zostało zdefiniowane
- Środowisko odniesienia jest potrzebne, jeśli podprogram można wywołać z dowolnego dowolnego miejsca w programie
- Język o statycznym zasięgu, który nie pozwala na zagnieżdżanie podprogramów, nie wymaga zamykania
- Zamknięcia są potrzebne tylko wtedy, gdy podprogram może uzyskać dostęp do zmiennych w zakresach zagnieżdżania i można go wywołać z dowolnego miejsca
- W celu obsługi zamknięć, implementacja może wymagać zapewnienia nieograniczonego zasięgu dla niektórych zmiennych (ponieważ podprogram może uzyskać dostęp do zmiennej nielokalnej, która normalnie nie jest już żywa)
Przykład
źródło
Oto kolejny przykład z życia i używanie popularnego w grach języka skryptowego - Lua. Musiałem nieznacznie zmienić sposób działania funkcji biblioteki, aby uniknąć problemu z niedostępnością standardowego wejścia.
Wartość old_dofile znika, gdy ten blok kodu kończy swój zasięg (ponieważ jest lokalny), jednak wartość została zamknięta w zamknięciu, więc nowa przedefiniowana funkcja dofile może uzyskać do niej dostęp, a raczej kopię przechowywaną wraz z funkcją jako „upvalue”.
źródło
Z Lua.org :
źródło
Jeśli jesteś ze świata Java, możesz porównać zamknięcie z funkcją członka klasy. Spójrz na ten przykład
Funkcja
g
jest zamknięciem:g
zamyka sięa
.g
Można ją więc porównać z funkcją składową,a
można porównać z polem klasy i funkcjąf
z klasą.źródło
Zamknięcia Ilekroć mamy funkcję zdefiniowaną w innej funkcji, funkcja wewnętrzna ma dostęp do zmiennych zadeklarowanych w funkcji zewnętrznej. Zamknięcia najlepiej wyjaśnić przykładami. Na listingu 2-18 widać, że funkcja wewnętrzna ma dostęp do zmiennej (variableInOuterFunction) z zewnętrznego zakresu. Zmienne w funkcji zewnętrznej zostały zamknięte (lub powiązane) funkcją wewnętrzną. Stąd termin zamknięcia. Sama koncepcja jest dość prosta i dość intuicyjna.
źródło: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf
źródło
Spójrz poniżej kodu, aby zrozumieć głębiej zamknięcie:
Tutaj, co zostanie wydrukowane?
0,1,2,3,4
nie to będzie5,5,5,5,5
tak z powodu zamknięciaJak to rozwiąże? Odpowiedź jest poniżej:
Pozwólcie, że wyjaśnię: kiedy funkcja nie utworzyła nic, dopóki nie wywołała pętli tak w pierwszym kodzie wywołanym 5 razy, ale nie została wywołana natychmiast, więc wywołana tj. Po 1 sekundzie, a także jest asynchroniczna, więc przed zakończeniem pętli i zapisaniem wartości 5 w var i i na końcu wykonaj
setTimeout
funkcję pięć razy i wydrukuj5,5,5,5,5
Tutaj jak to rozwiązać za pomocą IIFE, tzn. Natychmiastowego wywołania wyrażenia funkcji
Aby uzyskać więcej informacji, zapoznaj się z kontekstem wykonania, aby zrozumieć zamknięcie.
Jest jeszcze jedno rozwiązanie tego problemu za pomocą let (funkcja ES6), ale pod maską powyżej działa ta funkcja
=> Więcej wyjaśnień:
W pamięci, gdy dla pętli wykonaj obraz, wykonaj jak poniżej:
Pętla 1)
Pętla 2)
Pętla 3)
Pętla 4)
Pętla 5)
Tutaj nie jest wykonywane, a następnie po zakończeniu pętli var i zapisuje wartość 5 w pamięci, ale jej zasięg jest zawsze widoczny w funkcji potomnej, więc gdy funkcja jest wykonywana wewnątrz
setTimeout
stronę pięć razy, drukuje5,5,5,5,5
Aby rozwiązać ten problem, użyj IIFE, jak wyjaśniono powyżej.
źródło
Currying: Pozwala ci częściowo ocenić funkcję, przekazując tylko podzbiór jej argumentów. Rozważ to:
Zamknięcie: Zamknięcie jest niczym innym jak dostępem do zmiennej poza zakresem funkcji. Ważne jest, aby pamiętać, że funkcja wewnątrz funkcji lub funkcja zagnieżdżona nie jest zamknięciem. Zamknięcia są zawsze używane, gdy trzeba uzyskać dostęp do zmiennych poza zakresem funkcji.
źródło
Zamknięcie jest bardzo łatwe. Możemy to rozważyć w następujący sposób: Zamknięcie = funkcja + jego środowisko leksykalne
Rozważ następującą funkcję:
Jakie będzie zamknięcie w powyższej sprawie? Funkcja init () i zmienne w jej środowisku leksykalnym, tj. Name. Zamknięcie = init () + nazwa
Rozważ inną funkcję:
Jakie będą tutaj zamknięcia? Funkcja wewnętrzna może uzyskać dostęp do zmiennych funkcji zewnętrznej. displayName () może uzyskać dostęp do nazwy zmiennej zadeklarowanej w funkcji nadrzędnej, init (). Jednak te same zmienne lokalne w displayName () zostaną użyte, jeśli istnieją.
Zamknięcie 1: funkcja init + (nazwa zmiennej + funkcja displayName ()) -> zakres leksykalny
Zamknięcie 2: funkcja displayName + (nazwa zmiennej) -> zakres leksykalny
źródło
Zamknięcia zapewniają JavaScript ze stanem.
Stan w programowaniu oznacza po prostu zapamiętywanie rzeczy.
Przykład
W powyższym przypadku stan jest przechowywany w zmiennej „a”. Następnie dodajemy 1 do „a” kilka razy. Możemy to zrobić tylko dlatego, że jesteśmy w stanie „zapamiętać” wartość. Właściciel stanu „a” przechowuje tę wartość w pamięci.
Często w językach programowania chcesz śledzić rzeczy, zapamiętywać informacje i uzyskiwać do nich dostęp później.
W innych językach jest to zwykle realizowane za pomocą klas. Klasa, podobnie jak zmienne, śledzi swój stan. Z kolei instancje tej klasy również mają w sobie stan. Stan oznacza po prostu informacje, które możesz przechowywać i odzyskiwać później.
Przykład
Jak uzyskać dostęp do „wagi” z poziomu metody „renderowania”? Cóż, dzięki państwu. Każde wystąpienie klasy Chleb może nadać swój własny ciężar, czytając go z „stanu”, miejsca w pamięci, w którym moglibyśmy przechowywać te informacje.
Teraz JavaScript jest bardzo unikalnym językiem, który historycznie nie ma klas (teraz ma, ale pod maską są tylko funkcje i zmienne), więc Zamknięcia umożliwiają JavaScript zapamiętanie rzeczy i dostęp do nich później.
Przykład
Powyższy przykład osiągnął cel „utrzymania stanu” za pomocą zmiennej. To jest świetne! Ma to jednak tę wadę, że zmienna (właściciel „stanu”) jest teraz widoczna. Możemy zrobić lepiej. Możemy użyć Zamknięć.
Przykład
To jest fantastyczne.
Teraz nasza funkcja „count” może liczyć. Jest w stanie to zrobić, ponieważ może „zatrzymać” stan. W tym przypadku stanem jest zmienna „n”. Ta zmienna jest teraz zamknięta. Zamknięte w czasie i przestrzeni. Z czasem, ponieważ nigdy nie będziesz w stanie go odzyskać, zmienić, przypisać wartość lub bezpośrednio z nią współdziałać. W przestrzeni kosmicznej, ponieważ jest geograficznie zagnieżdżony w funkcji „countGenerator”.
Dlaczego to jest fantastyczne? Ponieważ bez angażowania innych wyrafinowanych i skomplikowanych narzędzi (np. Klas, metod, instancji itp.) Jesteśmy w stanie 1. ukryć 2. kontrolować na odległość
Kryjemy stan, zmienną „n”, co czyni ją zmienną prywatną! Stworzyliśmy również interfejs API, który może kontrolować tę zmienną w określony sposób. W szczególności możemy wywołać API w taki sposób, aby „count ()”, a to dodaje 1 do „n” z „odległości”. W żaden sposób, kształt ani forma nikt nie będzie mógł uzyskać dostępu do „n”, chyba że za pośrednictwem interfejsu API.
JavaScript jest naprawdę niesamowity w swojej prostocie.
Zamknięcia są w dużej mierze przyczyną tego.
źródło
Prosty przykład w Groovy w celach informacyjnych:
źródło