Od czasu do czasu widzę wzmiankę o „zamknięciach” i próbowałem to sprawdzić, ale Wiki nie podaje wyjaśnienia, które rozumiem. Czy ktoś mógłby mi pomóc tutaj?
language-features
closures
gablin
źródło
źródło
Odpowiedzi:
(Zastrzeżenie: jest to podstawowe wyjaśnienie; jeśli chodzi o definicję, trochę upraszczam)
Najprostszym sposobem na myślenie o zamknięciu jest funkcja, która może być przechowywana jako zmienna (określana jako „funkcja pierwszej klasy”), która ma specjalną zdolność dostępu do innych zmiennych lokalnych w zakresie, w którym została utworzona.
Przykład (JavaScript):
Funkcje 1 przypisane do
document.onclick
idisplayValOfBlack
są zamknięciami. Widać, że oba odnoszą się do zmiennej booleanblack
, ale ta zmienna jest przypisywana poza funkcją. Ponieważblack
jest lokalny dla zakresu, w którym funkcja została zdefiniowana , wskaźnik do tej zmiennej zostaje zachowany.Jeśli umieścisz to na stronie HTML:
To pokazuje, że oba mają dostęp do tego samego
black
i mogą być używane do przechowywania stanu bez żadnego obiektu opakowania.Wywołanie to
setKeyPress
ma pokazać, jak można przekazać funkcję tak jak każdą zmienną. Zakres zachowane w zamknięciu jest jeszcze jeden, w których funkcja została zdefiniowana.Zamknięcia są powszechnie używane jako procedury obsługi zdarzeń, szczególnie w JavaScript i ActionScript. Dobre użycie zamknięć pomoże ci w niejawny sposób powiązać zmienne z procedurami obsługi zdarzeń bez konieczności tworzenia opakowania obiektu. Jednak nieostrożne użycie doprowadzi do wycieków pamięci (na przykład gdy nieużywana, ale zachowana procedura obsługi zdarzeń jest jedyną rzeczą, która może zatrzymać duże obiekty w pamięci, zwłaszcza obiekty DOM, zapobiegając odśmiecaniu).
1: W rzeczywistości wszystkie funkcje w JavaScript są zamknięciami.
źródło
black
zadeklarowano wewnątrz funkcji, czy to nie zostanie zniszczone, gdy stos się rozwija?black
zadeklarowano wewnątrz funkcji, czy to nie zostanie zniszczone”. Pamiętaj również, że jeśli zadeklarujesz obiekt w funkcji, a następnie przypiszesz go do zmiennej, która żyje gdzie indziej, obiekt ten zostanie zachowany, ponieważ istnieją inne odwołania do niego.Zamknięcie to po prostu inny sposób patrzenia na przedmiot. Obiekt to dane, do których przypisana jest jedna lub więcej funkcji. Zamknięcie to funkcja, do której przypisana jest jedna lub więcej zmiennych. Oba są w zasadzie identyczne, przynajmniej na poziomie wdrożenia. Prawdziwa różnica polega na tym, skąd pochodzą.
W programowaniu obiektowym deklarujesz klasę obiektową, definiując z góry jej zmienne składowe i metody (funkcje składowe), a następnie tworzysz instancje tej klasy. Każde wystąpienie zawiera kopię danych elementu, zainicjowaną przez konstruktora. Następnie masz zmienną typu obiektu i przekazujesz ją jako fragment danych, ponieważ skupiamy się na jej naturze jako danych.
Z drugiej strony, w zamknięciu obiekt nie jest zdefiniowany z góry jak klasa obiektu, ani też nie jest tworzony w wywołaniu konstruktora w kodzie. Zamiast tego zapisujesz zamknięcie jako funkcję w innej funkcji. Zamknięcie może odnosić się do dowolnej zmiennej lokalnej funkcji zewnętrznej, a kompilator wykrywa ją i przenosi te zmienne z obszaru stosu funkcji zewnętrznej do deklaracji ukrytego obiektu zamknięcia. Następnie masz zmienną typu zamknięcia i chociaż jest to zasadniczo obiekt pod maską, przekazujesz ją jako odniesienie do funkcji, ponieważ fokus jest skupiony na jego naturze jako funkcji.
źródło
Termin zamknięcie wynika z faktu, że fragment kodu (blok, funkcja) może mieć dowolne zmienne, które są zamykane (tj. Powiązane z wartością) przez środowisko, w którym blok kodu jest zdefiniowany.
Weźmy na przykład definicję funkcji Scala:
W treści funkcji znajdują się dwie nazwy (zmienne)
v
ik
wskazujące dwie wartości całkowite. Nazwav
jest powiązana, ponieważ jest zadeklarowana jako argument funkcjiaddConstant
(patrząc na deklarację funkcji wiemy, żev
zostanie przypisana wartość po wywołaniu funkcji). Nazwak
jest wolna od funkcji,addConstant
ponieważ funkcja nie zawiera wskazówek co do tego, z jaką wartościąk
jest związana (i jak).Aby ocenić połączenie, takie jak:
musimy przypisać
k
wartość, co może się zdarzyć tylko wtedy, gdy nazwak
jest zdefiniowana w kontekście, w którymaddConstant
została zdefiniowana. Na przykład:Teraz, gdy zdefiniowaliśmy
addConstant
w kontekście, w którymk
jest zdefiniowany,addConstant
stało się zamknięciem, ponieważ wszystkie jego wolne zmienne są teraz zamknięte (powiązane z wartością):addConstant
można je wywoływać i przekazywać tak, jakby były funkcją. Zauważ, że wolna zmiennak
jest powiązana z wartością, gdy zdefiniowane jest zamknięcie , podczas gdy zmienna argumentuv
jest powiązana, gdy wywoływane jest zamknięcie .Zamknięcie jest więc w zasadzie funkcją lub blokiem kodu, który może uzyskiwać dostęp do wartości nielokalnych za pośrednictwem swoich wolnych zmiennych po tym, jak zostaną one powiązane kontekstem.
W wielu językach, jeśli użyjesz zamknięcia tylko raz, możesz uczynić je anonimowym , np
Zauważ, że funkcja bez wolnych zmiennych jest szczególnym przypadkiem zamknięcia (z pustym zestawem wolnych zmiennych). Analogicznie, anonimowa funkcja jest szczególnym przypadkiem anonimowego zamknięcia , tj. Anonimowa funkcja jest anonimowym zamknięciem bez wolnych zmiennych.
źródło
Proste wyjaśnienie w JavaScript:
alert(closure)
użyje wcześniej utworzonej wartościclosure
. PrzestrzeńalertValue
nazw zwróconej funkcji zostanie połączona z przestrzenią nazw, w którejclosure
znajduje się zmienna. Kiedy usuniesz całą funkcję, wartośćclosure
zmiennej zostanie usunięta, ale do tego czasualertValue
funkcja zawsze będzie mogła odczytać / zapisać wartość zmiennejclosure
.Jeśli uruchomisz ten kod, pierwsza iteracja przypisze wartość 0 do
closure
zmiennej i przepisze funkcję, aby:A ponieważ
alertValue
potrzebuje zmiennej lokalnejclosure
do wykonania funkcji, wiąże się z wartością poprzednio przypisanej zmiennej lokalnejclosure
.A teraz za każdym razem, gdy wywołujesz
closure_example
funkcję, wypisze ona przyrostową wartośćclosure
zmiennej, ponieważalert(closure)
jest powiązana.źródło
„Zamknięcie” to w gruncie rzeczy jakiś stan lokalny i jakiś kod, połączone w pakiet. Zazwyczaj stan lokalny pochodzi z otaczającego (leksykalnego) zakresu, a kod jest (zasadniczo) funkcją wewnętrzną, która jest następnie zwracana na zewnątrz. Zamknięcie jest wówczas kombinacją przechwyconych zmiennych, które widzi funkcja wewnętrzna i kod funkcji wewnętrznej.
Jest to jedna z tych rzeczy, która jest niestety trochę trudna do wyjaśnienia z powodu braku znajomości.
Jedną z analogii, którą z powodzeniem stosowałem w przeszłości było: „wyobraź sobie, że mamy coś, co nazywamy„ książką ”, w zamknięciu pokoju,„ książka ”to ta kopia, tam w rogu, TAOCP, ale na zamknięciu stołu , to ta kopia książki Dresden Files. Więc w zależności od tego, w jakim zamknięciu jesteś, kod „daj mi książkę” powoduje różne rzeczy. ”
źródło
static
zmienną lokalną można uznać za zamknięcie? Czy zamknięcia w Haskell obejmują państwo?static
zmienną lokalną, masz dokładnie jedną).Trudno zdefiniować, czym jest zamknięcie bez zdefiniowania pojęcia „państwa”.
Zasadniczo w języku z pełnym zasięgiem leksykalnym, który traktuje funkcje jako wartości pierwszej klasy, dzieje się coś wyjątkowego. Gdybym miał zrobić coś takiego:
Zmienna
x
nie tylko odwołuje się,function foo()
ale także odwołuje się do stanufoo
pozostawionego podczas ostatniego powrotu. Prawdziwa magia dzieje się, gdyfoo
inne funkcje są dalej zdefiniowane w jej zakresie; jest jak własne mini-środowisko (tak jak „normalnie” definiujemy funkcje w środowisku globalnym).Funkcjonalnie może rozwiązać wiele takich samych problemów jak słowo kluczowe „static” C ++ (C?), Które zachowuje stan zmiennej lokalnej podczas wielu wywołań funkcji; jednak bardziej przypomina stosowanie tej samej zasady (zmiennej statycznej) do funkcji, ponieważ funkcje są wartościami pierwszej klasy; zamknięcie dodaje obsługę stanu całej funkcji do zapisania (nie ma nic wspólnego z funkcjami statycznymi C ++).
Traktowanie funkcji jako wartości pierwszej klasy i dodanie obsługi zamknięć oznacza również, że możesz mieć więcej niż jedną instancję tej samej funkcji w pamięci (podobnie jak klasy). Oznacza to, że możesz ponownie użyć tego samego kodu bez konieczności resetowania stanu funkcji, co jest wymagane w przypadku zmiennych statycznych C ++ wewnątrz funkcji (może się mylić?).
Oto kilka testów wsparcia zamknięcia Lua.
wyniki:
Może być trudny i prawdopodobnie różni się w zależności od języka, ale w Lua wydaje się, że ilekroć funkcja jest wykonywana, jej stan jest resetowany. Mówię to, ponieważ wyniki z powyższego kodu byłyby inne, gdybyśmy uzyskiwali
myclosure
bezpośredni dostęp do funkcji / stanu (zamiast przez anonimową funkcję, zwraca), podobnie jak wpvalue
przypadku powrotu do wartości 10; ale jeśli uzyskamy dostęp do stanu myclosure przez x (anonimowa funkcja), możesz zobaczyć, żepvalue
jest żywy i dobrze gdzieś w pamięci. Podejrzewam, że jest w tym trochę więcej, być może ktoś może lepiej wyjaśnić naturę wdrożenia.PS: Nie znam opcji C ++ 11 (innych niż w poprzednich wersjach), więc zauważ, że to nie jest porównanie między zamknięciami w C ++ 11 i Lua. Ponadto wszystkie „linie narysowane” od Lua do C ++ są podobne do zmiennych statycznych i zamknięcia nie są w 100% takie same; nawet jeśli czasami są wykorzystywane do rozwiązywania podobnych problemów.
Nie jestem pewien, czy w powyższym przykładzie kodu funkcja anonimowa czy funkcja wyższego rzędu jest uważana za zamknięcie?
źródło
Zamknięcie to funkcja, która ma skojarzony stan:
W Perlu tworzysz takie zamknięcia:
Jeśli spojrzymy na nową funkcjonalność dostarczoną z C ++.
Pozwala również powiązać bieżący stan z obiektem:
źródło
Rozważmy prostą funkcję:
Ta funkcja nazywa się funkcją najwyższego poziomu, ponieważ nie jest zagnieżdżona w żadnej innej funkcji. Każda funkcja JavaScript wiąże ze sobą listę obiektów zwaną „łańcuchem zakresu” . Ten łańcuch zasięgu jest uporządkowaną listą obiektów. Każdy z tych obiektów definiuje niektóre zmienne.
W funkcjach najwyższego poziomu łańcuch zasięgu składa się z jednego obiektu, obiektu globalnego. Na przykład
f1
powyższa funkcja ma łańcuch zasięgu, który zawiera jeden obiekt, który definiuje wszystkie zmienne globalne. (zwróć uwagę, że termin „obiekt” tutaj nie oznacza obiektu JavaScript, to tylko obiekt zdefiniowany w implementacji, który działa jako kontener zmiennych, w którym JavaScript może „wyszukiwać” zmienne).Po wywołaniu tej funkcji JavaScript tworzy coś, co nazywa się „Obiektem aktywacyjnym” i umieszcza go na początku łańcucha zasięgu. Ten obiekt zawiera wszystkie zmienne lokalne (na przykład
x
tutaj). Stąd teraz mamy dwa obiekty w łańcuchu zasięgu: pierwszy to obiekt aktywacyjny, a pod nim obiekt globalny.Należy bardzo uważnie zauważyć, że dwa obiekty są umieszczane w łańcuchu zakresu w RÓŻNYCH czasach. Obiekt globalny jest wstawiany, gdy funkcja jest zdefiniowana (tj. Kiedy JavaScript parsuje funkcję i tworzy obiekt funkcji), a obiekt aktywacyjny wchodzi, gdy funkcja jest wywoływana.
Teraz wiemy to:
Sytuacja staje się interesująca, gdy mamy do czynienia z funkcjami zagnieżdżonymi. Stwórzmy jeden:
Po
f1
zdefiniowaniu otrzymujemy łańcuch zasięgu zawierający tylko obiekt globalny.Teraz, gdy
f1
zostanie wywołany, łańcuch zasięguf1
pobiera obiekt aktywacyjny. Ten obiekt aktywacyjny zawiera zmiennąx
i zmienną,f2
która jest funkcją. I zauważ, żef2
się definiuje. Dlatego w tym momencie JavaScript zapisuje również nowy łańcuch zasięguf2
. Łańcuch zakresu zapisany dla tej wewnętrznej funkcji jest obowiązującym bieżącym łańcuchem zakresu. Obecnie obowiązujący łańcuch zasięgu to łańcuchf1
. Stądf2
jest łańcuch zasięguf1
jest obecny łańcuch zakres - który zawiera obiekt aktywacjif1
i globalnej obiektu.Po
f2
wywołaniu otrzymuje własny obiekt aktywacyjny zawierającyy
, dodany do łańcucha zasięgu, który już zawiera obiekt aktywacyjnyf1
i obiekt globalny.Gdyby zdefiniowano w nim inną funkcję zagnieżdżoną
f2
, jej łańcuch zasięgu zawierałby trzy obiekty w czasie definicji (2 obiekty aktywacji dwóch funkcji zewnętrznych i obiekt globalny) oraz 4 w czasie wywołania.Teraz rozumiemy, jak działa łańcuch zasięgu, ale nie rozmawialiśmy jeszcze o zamknięciach.
Większość funkcji jest wywoływana przy użyciu tego samego łańcucha zasięgu, który obowiązywał, gdy funkcja została zdefiniowana, i nie ma znaczenia, że w grę wchodzi zamknięcie. Zamknięcia stają się interesujące, gdy są wywoływane w innym łańcuchu zasięgu niż ten, który obowiązywał podczas ich definiowania. Dzieje się tak najczęściej, gdy zagnieżdżony obiekt funkcji jest zwracany z funkcji, w której został zdefiniowany.
Gdy funkcja powraca, ten obiekt aktywacyjny jest usuwany z łańcucha zasięgu. Jeśli nie było zagnieżdżonych funkcji, nie ma już żadnych odniesień do obiektu aktywacyjnego i pobiera on śmieci. Jeśli zdefiniowano funkcje zagnieżdżone, każda z tych funkcji ma odniesienie do łańcucha zasięgu, a ten łańcuch zasięgu odnosi się do obiektu aktywacji.
Jeśli jednak te zagnieżdżone obiekty pozostaną w obrębie swojej funkcji zewnętrznej, wówczas one same zostaną wyrzucone do pamięci wraz z obiektem aktywacji, do którego się odnoszą. Ale jeśli funkcja definiuje funkcję zagnieżdżoną i zwraca ją lub przechowuje gdzieś we właściwości, wówczas będzie istniało zewnętrzne odniesienie do funkcji zagnieżdżonej. To nie będzie śmieci, a obiekt aktywacyjny, do którego się odnosi, również nie będzie śmieci.
W naszym przykładzie, nie wracają
f2
odf1
, a więc wtedy, gdy wywołanief1
wraca, jego przedmiotem aktywacyjny będzie usunięty z sieci zakres i śmieci zebrane. Ale gdybyśmy mieli coś takiego:W tym przypadku zwracany
f2
będzie łańcuch zasięgu, który będzie zawierał obiekt aktywacyjnyf1
, a zatem nie będzie śmieci. W tym momencie, jeśli zadzwonimyf2
, będzie mógł uzyskać dostęp dof1
zmiennej,x
nawet jeśli nas nie maf1
.Widzimy zatem, że funkcja utrzymuje z nią swój łańcuch zasięgu, a wraz z łańcuchem zasięgu przychodzą wszystkie obiekty aktywacyjne funkcji zewnętrznych. To jest istota zamknięcia. Mówimy, że funkcje w JavaScript mają „ zasięg leksykalny” , co oznacza, że zapisują zakres, który był aktywny, gdy był zdefiniowany, w przeciwieństwie do zakresu, który był aktywny, gdy zostały wywołane.
Istnieje wiele zaawansowanych technik programowania, które obejmują zamykanie, takie jak przybliżanie zmiennych prywatnych, programowanie sterowane zdarzeniami, częściowe zastosowanie itp.
Należy również pamiętać, że wszystko to dotyczy wszystkich języków, które obsługują zamknięcia. Na przykład PHP (5.3+), Python, Ruby itp.
źródło
Zamknięcie to optymalizacja kompilatora (inaczej cukier syntaktyczny?). Niektórzy nazywają to także Obiektem Biednego Człowieka .
Zobacz odpowiedź Erica Lipperta : (fragment poniżej)
Kompilator wygeneruje kod w następujący sposób:
Ma sens?
Poprosiłeś także o porównania. Zarówno VB, jak i JScript tworzą zamknięcia w bardzo podobny sposób.
źródło