Co to jest zamknięcie?

155

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?

gablin
źródło
Jeśli znasz Java / C # mam nadzieję, że ten link pomoże
Gulshan
1
Zamknięcia są trudne do zrozumienia. Powinieneś spróbować kliknąć wszystkie linki w pierwszym zdaniu tego artykułu w Wikipedii i najpierw zrozumieć te artykuły.
Zach
3
Jaka jest jednak podstawowa różnica między zamknięciem a klasą? Okej, klasa z tylko jedną publiczną metodą.
biziclop
5
@biziclop: Ty mógł naśladować zamknięcie z klasy (to co deweloperów Java trzeba zrobić). Ale zazwyczaj są nieco mniej gadatliwi i nie musisz ręcznie zarządzać tym, co kręcisz. (Twarde seplenienie zadają podobne pytanie, ale prawdopodobnie dochodzą do innego wniosku - że obsługa OO na poziomie języka nie jest konieczna, gdy masz zamknięcia).

Odpowiedzi:

141

(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):

var setKeyPress = function(callback) {
    document.onkeypress = callback;
};

var initialize = function() {
    var black = false;

    document.onclick = function() {
        black = !black;
        document.body.style.backgroundColor = black ? "#000000" : "transparent";
    }

    var displayValOfBlack = function() {
        alert(black);
    }

    setKeyPress(displayValOfBlack);
};

initialize();

Funkcje 1 przypisane do document.onclicki displayValOfBlacksą zamknięciami. Widać, że oba odnoszą się do zmiennej boolean black, ale ta zmienna jest przypisywana poza funkcją. Ponieważ blackjest 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:

  1. Kliknij, aby zmienić na czarny
  2. Naciśnij [Enter], aby zobaczyć „prawda”
  3. Kliknij ponownie, zmieni się z powrotem na biały
  4. Naciśnij [enter], aby zobaczyć „false”

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 setKeyPressma 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.

Nicole
źródło
3
Gdy czytałem twoją odpowiedź, poczułem, że w mojej głowie zapala się żarówka. Bardzo mile widziane! :)
Jay
1
Skoro blackzadeklarowano wewnątrz funkcji, czy to nie zostanie zniszczone, gdy stos się rozwija?
gablin
1
@gablin, to jest unikalne w językach, które mają zamknięcia. Wszystkie języki z funkcją odśmiecania działają w ten sam sposób - gdy nie ma już żadnych odwołań do obiektu, można go zniszczyć. Ilekroć funkcja jest tworzona w JS, zasięg lokalny jest związany z tą funkcją, dopóki ta funkcja nie zostanie zniszczona.
Nicole
2
@gablin, to dobre pytanie. Nie sądzę, że mogą nie & mdash; ale wspomniałem tylko o wyrzucaniu elementów bezużytecznych od czasu, gdy używa tego JS i do tego zdawało się mieć odniesienie, kiedy powiedziałeś „skoro blackzadeklarowano 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.
Nicole,
1
Cel C (i C pod clangiem) obsługuje bloki, które są w zasadzie zamknięciami, bez odśmiecania. Wymaga wsparcia środowiska wykonawczego i ręcznej interwencji w zakresie zarządzania pamięcią.
quixoto,
68

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.

Mason Wheeler
źródło
3
+1: Dobra odpowiedź. Zamknięcie można zobaczyć jako obiekt za pomocą tylko jednej metody, a dowolny obiekt jako zbiór zamknięć dla niektórych wspólnych podstawowych danych (zmiennych składowych obiektu). Myślę, że te dwa poglądy są dość symetryczne.
Giorgio
3
Bardzo dobra odpowiedź. To faktycznie wyjaśnia wgląd zamknięcia.
RoboAlex,
1
@Mason Wheeler: Gdzie przechowywane są dane zamknięcia? W stosie jak funkcja? Lub w kupie jak obiekt?
RoboAlex,
1
@RoboAlex: W stercie, ponieważ jest to obiekt, który wygląda jak funkcja.
Mason Wheeler,
1
@RoboAlex: Miejsce przechowywania zamknięcia i przechwyconych danych zależy od implementacji. W C ++ może być przechowywany na stercie lub na stosie.
Giorgio
29

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:

def addConstant(v: Int): Int = v + k

W treści funkcji znajdują się dwie nazwy (zmienne) vi kwskazujące dwie wartości całkowite. Nazwa vjest powiązana, ponieważ jest zadeklarowana jako argument funkcji addConstant(patrząc na deklarację funkcji wiemy, że vzostanie przypisana wartość po wywołaniu funkcji). Nazwa kjest wolna od funkcji, addConstantponieważ funkcja nie zawiera wskazówek co do tego, z jaką wartością kjest związana (i jak).

Aby ocenić połączenie, takie jak:

val n = addConstant(10)

musimy przypisać kwartość, co może się zdarzyć tylko wtedy, gdy nazwa kjest zdefiniowana w kontekście, w którym addConstantzostała zdefiniowana. Na przykład:

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  def addConstant(v: Int): Int = v + k

  values.map(addConstant)
}

Teraz, gdy zdefiniowaliśmy addConstantw kontekście, w którym kjest zdefiniowany, addConstantstało się zamknięciem, ponieważ wszystkie jego wolne zmienne są teraz zamknięte (powiązane z wartością): addConstantmożna je wywoływać i przekazywać tak, jakby były funkcją. Zauważ, że wolna zmienna kjest powiązana z wartością, gdy zdefiniowane jest zamknięcie , podczas gdy zmienna argumentu vjest 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

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  values.map(v => v + k)
}

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.

Giorgio
źródło
To dobrze sprawdza się w logicznych formułach zamkniętych i otwartych. Dzięki za odpowiedź.
RainDoctor,
@RainDoctor: Wolne zmienne są definiowane w formułach logicznych i wyrażeniach rachunku lambda w podobny sposób: lambda w wyrażeniu lambda działa jak kwantyfikator w formułach logicznych wrt zmienne / powiązane.
Giorgio
9

Proste wyjaśnienie w JavaScript:

var closure_example = function() {
    var closure = 0;
    // after first iteration the value will not be erased from the memory
    // because it is bound with the returned alertValue function.
    return {
        alertValue : function() {
            closure++;
            alert(closure);
        }
    };
};
closure_example();

alert(closure)użyje wcześniej utworzonej wartości closure. Przestrzeń alertValuenazw zwróconej funkcji zostanie połączona z przestrzenią nazw, w której closureznajduje się zmienna. Kiedy usuniesz całą funkcję, wartość closurezmiennej zostanie usunięta, ale do tego czasu alertValuefunkcja zawsze będzie mogła odczytać / zapisać wartość zmiennej closure.

Jeśli uruchomisz ten kod, pierwsza iteracja przypisze wartość 0 do closurezmiennej i przepisze funkcję, aby:

var closure_example = function(){
    alertValue : function(){
        closure++;
        alert(closure);
    }       
}

A ponieważ alertValuepotrzebuje zmiennej lokalnej closuredo wykonania funkcji, wiąże się z wartością poprzednio przypisanej zmiennej lokalnej closure.

A teraz za każdym razem, gdy wywołujesz closure_examplefunkcję, wypisze ona przyrostową wartość closurezmiennej, ponieważ alert(closure)jest powiązana.

closure_example.alertValue()//alerts value 1 
closure_example.alertValue()//alerts value 2 
closure_example.alertValue()//alerts value 3
//etc. 
Muha
źródło
dzięki, nie testowałem kodu =) wszystko wydaje się teraz w porządku.
Muha
5

„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. ”

Vatine
źródło
Zapomniałeś tego: en.wikipedia.org/wiki/Closure_(computer_programming) w swojej odpowiedzi.
S.Lott
3
Nie, świadomie postanowiłem nie zamykać tej strony.
Vatine
„Stan i funkcja.”: Czy funkcję C ze staticzmienną lokalną można uznać za zamknięcie? Czy zamknięcia w Haskell obejmują państwo?
Giorgio,
2
@Giorgio Closures w Haskell zbliżają się (jak sądzę) do argumentów w zakresie leksykalnym, w którym są zdefiniowane, więc powiedziałbym „tak” (chociaż w najlepszym razie nie znam Haskell). Funkcja AC ze zmienną statyczną jest w najlepszym razie bardzo ograniczonym zamknięciem (naprawdę chcesz być w stanie utworzyć wiele zamknięć z jednej funkcji, ze staticzmienną lokalną, masz dokładnie jedną).
Vatine
Zadałem to pytanie celowo, ponieważ uważam, że funkcja C ze zmienną statyczną nie jest zamknięciem: zmienna statyczna jest zdefiniowana lokalnie i znana tylko w zamknięciu, nie ma dostępu do środowiska. Ponadto nie jestem w 100% pewien, ale sformułowałbym twoje zdanie na odwrót: używasz mechanizmu zamknięcia do tworzenia różnych funkcji (funkcja jest definicją zamknięcia + powiązanie jej wolnych zmiennych).
Giorgio
5

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:

function foo(x)
return x
end

x = foo

Zmienna xnie tylko odwołuje się, function foo()ale także odwołuje się do stanu foopozostawionego podczas ostatniego powrotu. Prawdziwa magia dzieje się, gdy fooinne 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.

--Closure testing
--By Trae Barlow
--

function myclosure()
    print(pvalue)--nil
    local pvalue = pvalue or 10
    return function()
        pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
        print(pvalue)
        pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
        return pvalue
    end
end

x = myclosure() --x now references anonymous function inside myclosure()

x()--nil, 20
x() --21, 31
x() --32, 42
    --43, 53 -- if we iterated x() again

wyniki:

nil
20
31
42

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 myclosurebezpośredni dostęp do funkcji / stanu (zamiast przez anonimową funkcję, zwraca), podobnie jak w pvalueprzypadku powrotu do wartości 10; ale jeśli uzyskamy dostęp do stanu myclosure przez x (anonimowa funkcja), możesz zobaczyć, że pvaluejest ż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?

Trae Barlow
źródło
4

Zamknięcie to funkcja, która ma skojarzony stan:

W Perlu tworzysz takie zamknięcia:

#!/usr/bin/perl

# This function creates a closure.
sub getHelloPrint
{
    # Bind state for the function we are returning.
    my ($first) = @_;a

    # The function returned will have access to the variable $first
    return sub { my ($second) = @_; print  "$first $second\n"; };
}

my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");

&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World

Jeśli spojrzymy na nową funkcjonalność dostarczoną z C ++.
Pozwala również powiązać bieżący stan z obiektem:

#include <string>
#include <iostream>
#include <functional>


std::function<void(std::string const&)> getLambda(std::string const& first)
{
    // Here we bind `first` to the function
    // The second parameter will be passed when we call the function
    return [first](std::string const& second) -> void
    {   std::cout << first << " " << second << "\n";
    };
}

int main(int argc, char* argv[])
{
    auto hw = getLambda("Hello");
    auto gw = getLambda("GoodBye");

    hw("World");
    gw("World");
}
Martin York
źródło
2

Rozważmy prostą funkcję:

function f1(x) {
    // ... something
}

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 f1powyż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 xtutaj). 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:

  • Z każdą funkcją jest powiązany łańcuch zasięgu
  • Kiedy funkcja jest zdefiniowana (kiedy obiekt funkcji jest tworzony), JavaScript zapisuje łańcuch zasięgu z tą funkcją
  • W przypadku funkcji najwyższego poziomu łańcuch zasięgu zawiera tylko obiekt globalny w czasie definicji funkcji i dodaje dodatkowy obiekt aktywacji na górze w czasie wywołania

Sytuacja staje się interesująca, gdy mamy do czynienia z funkcjami zagnieżdżonymi. Stwórzmy jeden:

function f1(x) {

    function f2(y) {
        // ... something
    }

}

Po f1zdefiniowaniu otrzymujemy łańcuch zasięgu zawierający tylko obiekt globalny.

Teraz, gdy f1zostanie wywołany, łańcuch zasięgu f1pobiera obiekt aktywacyjny. Ten obiekt aktywacyjny zawiera zmienną xi zmienną, f2która jest funkcją. I zauważ, że f2się definiuje. Dlatego w tym momencie JavaScript zapisuje również nowy łańcuch zasięgu f2. Ł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ńcuch f1. Stąd f2jest łańcuch zasięgu f1jest obecny łańcuch zakres - który zawiera obiekt aktywacji f1i globalnej obiektu.

Po f2wywołaniu otrzymuje własny obiekt aktywacyjny zawierający y, dodany do łańcucha zasięgu, który już zawiera obiekt aktywacyjny f1i 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.

Połączenie obiektu funkcji i zakresu (zestawu powiązań zmiennych), w którym zmienne funkcji są rozwiązywane, nazywa się zamknięciem w literaturze informatycznej - JavaScript, ostateczny przewodnik Davida Flanagana

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ą f2od f1, a więc wtedy, gdy wywołanie f1wraca, jego przedmiotem aktywacyjny będzie usunięty z sieci zakres i śmieci zebrane. Ale gdybyśmy mieli coś takiego:

function f1(x) {

    function f2(y) {
        // ... something
    }

    return f2;
}

W tym przypadku zwracany f2będzie łańcuch zasięgu, który będzie zawierał obiekt aktywacyjny f1, a zatem nie będzie śmieci. W tym momencie, jeśli zadzwonimy f2, będzie mógł uzyskać dostęp do f1zmiennej, xnawet jeśli nas nie ma f1.

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.

treecoder
źródło
-1

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:

private class Locals
{
  public int count;
  public void Anonymous()
  {
    this.count++;
  }
}

public Action Counter()
{
  Locals locals = new Locals();
  locals.count = 0;
  Action counter = new Action(locals.Anonymous);
  return counter;
}

Ma sens?
Poprosiłeś także o porównania. Zarówno VB, jak i JScript tworzą zamknięcia w bardzo podobny sposób.

LamonteCristo
źródło
Ta odpowiedź to CW, ponieważ nie zasługuję na punkty za świetną odpowiedź Erica. Głosuj za tym, co uważasz za stosowne. HTH
goodguys_activate
3
-1: Twoje wyjaśnienie jest zbyt root w C #. Zamknięcie jest używane w wielu językach i jest czymś więcej niż cukrem syntaktycznym w tych językach i obejmuje zarówno funkcję, jak i stan.
Martin York
1
Nie, zamknięcie nie jest ani „optymalizacją kompilatora”, ani cukrem syntaktycznym. -1