86 Pytanie: Jak działają zamknięcia JavaScript?

pytanie utworzone w Sun, Apr 9, 2017 12:00 AM

Jak wyjaśniłbyś zamknięcie JavaScript komuś, kto ma wiedzę na temat pojęć, z których się składa (na przykład funkcje, zmienne i tym podobne), ale sam nie rozumie zamknięć?

Widziałem przykład schematu podany na Wikipedii, ale niestety tak się stało nie pomóż.

    
7649
  1. Moim problemem z tymi i wieloma odpowiedziami jest to, że podchodzą do tego z abstrakcyjnej, teoretycznej perspektywy, zamiast zaczynać od wyjaśnienia, dlaczego zamknięcia są konieczne w JavaScript i praktycznych sytuacjach w którym ich używasz. Skończyło się na tym, że musisz przejść przez cały czas, myśląc „ale dlaczego?”. Po prostu zacznę od: closures to zgrabny sposób radzenia sobie z następującymi dwiema rzeczywistościami JavaScript: a. zakres znajduje się na poziomie funkcji, a nie na poziomie bloku i, b. wiele z tego, co robisz w praktyce w JavaScript, jest sterowane asynchronicznie /zdarzeniami.
    2013-03-08 17: 22: 59Z
  2. @ Redsandro Po pierwsze, znacznie ułatwia pisanie kodu sterowanego zdarzeniami. Mogę uruchomić funkcję, gdy strona ładuje się, aby określić szczegóły dotyczące HTML lub dostępnych funkcji. Mogę zdefiniować i ustawić procedurę obsługi w tej funkcji i mieć dostęp do wszystkich informacji kontekstowych za każdym razem, gdy wywoływana jest procedura obsługi bez konieczności ponownego zapytania. Rozwiąż problem raz, użyj go ponownie na każdej stronie, na której ten moduł obsługi jest potrzebny, ze zmniejszonym narzutem na ponowne wywołanie programu obsługi. Czy kiedykolwiek zobaczyłeś, że te same dane są ponownie mapowane dwukrotnie w języku, który ich nie ma? Zamknięcia znacznie ułatwiają unikanie tego typu rzeczy.
    2013-06-26 17: 02: 16Z
  3. Dla programistów Javy krótką odpowiedzią jest to, że jest to odpowiednik funkcji klasy wewnętrznej. Klasa wewnętrzna przechowuje także niejawny wskaźnik do instancji klasy zewnętrznej i jest używana w tym samym celu (to znaczy do tworzenia procedur obsługi zdarzeń).
    2014-06-19 10: 04: 21Z
  4. Zrozumiałem to znacznie lepiej stąd: javascriptissexy.com/understand-javascript-closures-with-ease . Nadal potrzebowałem zamknięcia po zamknięciu po przeczytaniu pozostałych odpowiedzi. :)
    2016-01-22 05: 41: 32Z
  5. Uznałem ten praktyczny przykład za bardzo przydatny: youtube.com/watch?v=w1s9PgtEoJs
    2016-07-06 17: 33: 55Z
30 odpowiedzi                              30                         

Zamknięcia JavaScript dla początkujących

Zgłoszony przez Morrisa w Tue, 2006-02-21 10:19. Edytowane przez społeczność od.

Zamknięcia nie są magiczne

Ta strona wyjaśnia zamknięcia, aby programista mógł je zrozumieć - używając działającego kodu JavaScript. Nie jest dla guru ani programistów funkcjonalnych.

Zamknięcia są nie trudne , aby zrozumieć, kiedy podstawowa koncepcja jest zakłopotana. Jednak nie można ich zrozumieć, czytając teoretyczne lub akademickie wyjaśnienia!

Ten artykuł jest przeznaczony dla programistów z pewnym doświadczeniem programistycznym w głównym nurcie języka i którzy mogą czytać następującą funkcję JavaScript:

 
function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Dwa krótkie podsumowania

  • Gdy funkcja (foo) deklaruje inne funkcje (bar i baz), rodzina zmiennych lokalnych utworzonych w foo jest nie zniszczona , gdy funkcja kończy działanie. Zmienne stają się niewidzialne dla świata zewnętrznego. foo może więc sprytnie zwrócić funkcje bar i baz i mogą nadal czytać, pisać i komunikować sięłączą się ze sobą za pomocą tej zamkniętej rodziny zmiennych („zamknięcie”), w którą nikt inny nie może się wtrącać, nawet osoby, która w przyszłości ponownie zadzwoni do foo.

  • Zamknięcie jest jednym ze sposobów wspierania funkcji pierwszej klasy ; jest to wyrażenie, które może odwoływać się do zmiennych w jego zakresie (gdy został po raz pierwszy zadeklarowany), być przypisane do zmiennej, być przekazywane jako argument funkcji lub być zwrócone jako wynik funkcji.

Przykład zamknięcia

Poniższy kod zwraca odwołanie do funkcji:

 
function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

Większość programistów JavaScript zrozumie, w jaki sposób odwołanie do funkcji jest zwracane do zmiennej (say2) w powyższym kodzie. Jeśli tego nie zrobisz, musisz się temu przyjrzeć, zanim nauczysz się zamykania. Programista używający C pomyślałby o funkcji zwracającej wskaźnik do funkcji, a zmienne say i say2 były wskaźnikami do funkcji.

Istnieje krytyczna różnica między wskaźnikiem C a funkcją a odwołaniem do JavaScript do funkcji. W JavaScript możesz myśleć o zmiennej referencyjnej funkcji jako posiadającej zarówno wskaźnik do funkcji , jak i jako ukryty wskaźnik do zamknięcia.

Powyższy kod ma zamknięcie, ponieważ anonimowa funkcja function() { console.log(text); } została zadeklarowana wewnątrz innej funkcji, sayHello2() w tym przykładzie. W JavaScript, jeśli użyjesz słowa kluczowego function w innej funkcji, tworzysz zamknięcie.

W C i większości innych popularnych języków po funkcja zwraca, wszystkie zmienne lokalne nie są już dostępne, ponieważ ramka stosu jest zniszczona.

W JavaScript, jeśli deklarujesz funkcję w innej funkcji, lokalne zmienne funkcji zewnętrznej mogą pozostać dostępne po powrocie z niej. Zostało to pokazane powyżej, ponieważ wywołujemy funkcję say2() po zwróceniu z sayHello2(). Zauważ, że kod, który nazywamy, odwołuje się do zmiennej text, która była lokalną zmienną funkcji sayHello2().

 
function() { console.log(text); } // Output of say2.toString();

Patrząc na wynik say2.toString(), widzimy, że kod odnosi się do zmiennej text. Funkcja anonimowa może odwoływać się do text, która przechowuje wartość 'Hello Bob', ponieważ zmienne lokalne sayHello2() zostały potajemnie utrzymane przy życiu w zamknięciu.

Geniusz polega na tym, że w JavaScript odwołanie do funkcji ma również tajne odniesienie do zamknięcia, w którym zostało utworzone - podobnie jak delegaci są wskaźnikiem metody plus tajne odniesienie do obiektu.

Więcej przykładów

Z jakiegoś powodu zamknięcie wydaje się bardzo trudne do zrozumienia, kiedy o nich czytasz, ale kiedy zobaczysz kilka przykładów, staje się jasne, jak działają (zajęło mi to trochę czasu). Polecam uważnie przeglądać przykłady, aż zrozumiesz, jak działają. Jeśli zaczniesz używać zamknięć bez pełnego zrozumienia ich działania, wkrótce stworzyłbyś kilka bardzo dziwnych błędów!

Przykład 3

Ten przykład pokazuje, że zmienne lokalne nie są kopiowane - są przechowywane przez odniesienie. To tak, jakby ramka stosu pozostała żywa w pamięci nawet po zamknięciu zewnętrznej funkcji!

 
function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Przykład 4

Wszystkie trzy funkcje globalne mają wspólne odniesienie do zamknięcia tego samego , ponieważ wszystkie są zadeklarowane w jednym wywołaniu do setupSomeGlobals().

 
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Trzy funkcje mają współdzielony dostęp do tego samego zamknięcia - lokalne zmienne setupSomeGlobals(), gdy trzy funkcje zostały zdefiniowane.

Zauważ, że w powyższym przykładzie, jeśli ponownie wywołasz setupSomeGlobals(), zostanie utworzone nowe zamknięcie (ramka stosu!). Stare gLogNumber, gIncreaseNumber, gSetNumber zmienne są nadpisywane przez nowe funkcje, które mają nowe zamknięcie. (W JavaScript, ilekroć deklarujesz funkcję w innej funkcji, wewnętrzne funkcje są /są ponownie odtwarzane każdy czas wywoływania funkcji zewnętrznej.)

Przykład 5

Ten przykład pokazuje, że zamknięcie zawiera dowolne zmienne lokalne, które zostały zadeklarowane wewnątrz funkcji zewnętrznej przed jej zamknięciem. Zauważ, że zmienna alice jest faktycznie zadeklarowana po funkcji anonimowej. Funkcja anonimowa jest deklarowana jako pierwsza, a gdy ta funkcja jest wywoływana, może uzyskać dostęp do zmiennej alice, ponieważ alice znajduje się w tym samym zakresie (JavaScript wykonuje zmienne podnoszenie ). Również sayAlice()() bezpośrednio wywołuje odwołanie do funkcji zwrócone z sayAlice() - jest dokładnie takie samo jak to, co zostało wcześniej zrobione, ale bez tymczasowej zmiennej.

 
function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Podstępne: zauważ, że zmienna say jest również wewnątrz zamknięcia i może być dostępna dla każdej innej funkcji, która może być zadeklarowana w sayAlice(), lub może być dostępna rekurencyjnie w obrębie funkcji wewnętrznej.

Przykład 6

Ten jest prawdziwy dla wielu ludzi, więc musisz to zrozumieć. Bądź bardzo ostrożny, jeśli definiujesz funkcję w pętli: zmienne lokalne z zamknięcia mogą nie działać tak, jak mogłoby się wydawać.

Aby zrozumieć ten przykład, musisz zrozumieć funkcję „zmiennego podnoszenia” w JavaScript.

 
function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

Wiersz result.push( function() {console.log(item + ' ' + list[i])} dodaje trzykrotnie odwołanie do anonimowej funkcji do tablicy wyników. Jeśli nie jesteś zaznajomiony z anonimowymi funkcjami, pomyśl o tym tak:

 
pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Zauważ, że po uruchomieniu przykładu "item2 undefined" jest rejestrowany trzy razy! Dzieje się tak, ponieważ podobnie jak w poprzednich przykładach, istnieje tylko jedno zamknięcie dla zmiennych lokalnych dla buildList (result, i, list i item). Gdy anonimowe funkcje są wywoływane w linii fnlist[j](); wszystkie używają tego samego pojedynczego zamknięcia i używają bieżącej wartości i i item w tym jednym zamknięciu (gdzie i ma wartość 3, ponieważ pętla została ukończona, a item ma wartość 'item2'). Uwaga: indeksujemy od 0, stąd item ma wartość item2. I ++ zwiększy i do wartości 3.

Pomocne może być sprawdzenie, co się dzieje, gdy używana jest deklaracja na poziomie bloku zmiennej item (za pomocą słowa kluczowego let) zamiast deklaracji zmiennej o zakresie funkcji za pomocą słowa kluczowego var. Jeśli ta zmiana zostanie wprowadzona, każda anonimowa funkcja w tablicy result ma swoje własne zamknięcie; gdy przykład zostanie uruchomiony, dane wyjściowe są następujące:

 
item0 undefined
item1 undefined
item2 undefined

Jeśli zmienna i jest również zdefiniowana przy użyciu let zamiast var, to wynikiem jest:

 
item0 1
item1 2
item2 3

Przykład 7

W tym ostatnim przykładzie każde wywołanie funkcji głównej tworzy oddzielne zamknięcie.

 
function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Podsumowanie

Jeśli wszystko wydaje się całkowicie niejasne, najlepszym rozwiązaniem jest skorzystanie z przykładów. Czytanie wyjaśnień jest o wiele trudniejsze niż zrozumienie przykładów. Moje wyjaśnienia dotyczące zamknięć i ramek stosu itp. Nie są poprawne technicznie - są to rażące uproszczenia, które mają pomóc zrozumieć. Po opanowaniu podstawowej idei możesz pobrać szczegóły później.

Punkty końcowe:

  • Zawsze, gdy używasz function w innej funkcji, używane jest zamknięcie.
  • Zawsze, gdy używasz eval() wewnątrz funkcji, używane jest zamknięcie. Tekst, który eval może odwoływać się do zmiennych lokalnych funkcji, aw eval można nawet utworzyć nowe zmienne lokalne przy użyciu eval('var foo = …')
  • Kiedy używasz new Function(…) ( Konstruktor funkcji ) wewnątrz funkcji, nie tworzy zamknięcia. (Nowa funkcja nie może odwoływać się do zmiennych lokalnych funkcji zewnętrznej.)
  • Zamknięcie w JavaScript jest jak przechowywanie kopii wszystkich zmiennych lokalnych, tak jak w przypadku wyjścia z funkcji.
  • Prawdopodobnie najlepiej jest myśleć, że zamknięcie zawsze tworzy tylko wpis do funkcji, a zmienne lokalne są dodawane do tego zamknięcia.
  • Nowy zestaw zmiennych lokalnych jest zachowywany za każdym razem, gdy wywoływana jest funkcja z zamknięciem (biorąc pod uwagę, że funkcja zawiera w sobie deklarację funkcji, a odwołanie do tej funkcji wewnętrznej jest zwracane lub zachowywane jest dla niej odwołanie zewnętrzne) w jakiś sposób).
  • Dwie funkcje mogą wyglądać tak, jakby miały ten sam tekst źródłowy, ale mają zupełnie inne zachowanie ze względu na ich „ukryte” zamknięcie. Nie sądzę, aby kod JavaScript mógł się dowiedzieć, czy odwołanie do funkcji ma zamknięcie, czy nie.
  • Jeśli próbujesz wykonać jakiekolwiek modyfikacje dynamicznego kodu źródłowego (na przykład: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), to nie zadziała, jeśli myFunction jest zamknięciem (oczywiście nigdy nawet nie pomyślałbyś o zastąpieniu ciągu kodu źródłowego w czasie wykonywania, ale ...).
  • Możliwe jest uzyskanie deklaracji funkcji w deklaracjach funkcji w ramach funkcji… i można uzyskać zamknięcia na więcej niż jednym poziomie.
  • Myślę, że normalnie zamknięcie jest terminem zarówno dla funkcji, jak i przechwyconych zmiennych. Zauważ, że nie używam tej definicji w tym artykule!
  • Podejrzewam, że zamknięcia w JavaScript różnią się od tych zwykle występujących w językach funkcjonalnych.

Linki

Dziękujemy

Jeśli masz właśnie poznane zamknięcia (tutaj lub gdzie indziej!), to jestem zainteresowany wszelkimi informacjami zwrotnymi na temat ewentualnych zmian, które możesz zasugerować, aby uczynić ten artykuł jaśniejszym. Wyślij e-mail do morrisjohns.com (morris_closure @). Pamiętaj, że nie jestem guru w JavaScript - ani w zamknięciach.


Oryginalny post Morrisa można znaleźć w Archiwum Internetowym .

    
6854
2019-06-05 17: 50: 04Z
  1. Brilliant. Szczególnie kocham: „Zamknięcie w JavaScript jest jak przechowywanie kopii wszystkich lokalnych zmiennych, tak jak w przypadku wyjścia z funkcji.”
    2008-09-21 14: 38: 40Z
  2. @ e-satis - Może się wydawać genialny, "kopia wszystkich zmiennych lokalnych, tak jak wtedy, gdy funkcja wyszła" jest myląca. Sugeruje, że wartości zmiennych są kopiowane, ale tak naprawdę jest to sam zestaw zmiennych, który nie zmienia się po wywołaniu funkcji (może z wyjątkiem „eval”: blog.rakeshpai.me/2008/10/… ). Sugeruje, że funkcja musi powrócić przed utworzeniem zamknięcia, ale nie musi powrócić, zanim zamknięcie może zostać wykorzystane jako zamknięcie.
    2011-08-08 15: 24: 48Z
  3. To brzmi ładnie: "Zamknięcie w JavaScript jest jak przechowywanie kopii wszystkich lokalnych zmiennych, tak jak w przypadku wyjścia funkcji." Jest to jednak mylące z kilku powodów. (1) Wywołanie funkcji nie musi wychodzić, aby utworzyć zamknięcie. (2) Nie jest to kopia wartości zmiennych lokalnych, ale same zmienne. (3) Nie mówi, kto ma dostęp do tych zmiennych.
    2013-02-11 18: 20: 22Z
  4. Przykład 5 pokazuje „gotcha”, gdzie kod nie działa zgodnie z przeznaczeniem. Ale nie pokazuje, jak to naprawić. Ta inna odpowiedź pokazuje, jak to zrobić.
    2013-06-24 19: 12: 21Z
  5. Podoba mi się, że ten post zaczyna się dużymi, pogrubionymi literami, mówiącymi, że "Closures Are Not Magic" i kończy swój pierwszy przykład z "Magią jest to, że w JavaScript jest także odniesieniem do funkcji ma tajne odniesienie do zamknięcia, w którym zostało utworzone „.
    2014-09-25 02: 30: 17Z

Zawsze, gdy widzisz słowo kluczowe function w innej funkcji, funkcja wewnętrzna ma dostęp do zmiennych w funkcji zewnętrznej.

 
function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Będzie to zawsze rejestrować 16, ponieważ bar może uzyskać dostęp do x, który został zdefiniowany jako argument do foo, a także może uzyskać dostęp do tmp z foo.

To is zamknięcie. Funkcja nie musi zwracać , aby nazywać się zamknięciem. Po prostu dostęp do zmiennych poza bezpośrednim zakresem leksykalnym powoduje zamknięcie .

 
function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

Powyższa funkcja będzie również logować 16, ponieważ bar nadal może odnosić się do x i tmp, nawet jeśli nie jest już bezpośrednio w zasięgu.

Ponieważ jednak tmp wciąż kręci się w zamknięciu bar, jest również zwiększany. Będzie zwiększana za każdym razem, gdy zadzwonisz pod numer bar.

Najprostszym przykładem zamknięcia jest:

 
var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Gdy wywoływana jest funkcja JavaScript, tworzony jest nowy kontekst wykonania. Wraz z argumentami funkcji i obiektem nadrzędnym ten kontekst wykonania odbiera również wszystkie zmienne zadeklarowane poza nim (w powyższym przykładzie, zarówno „a”, jak i „b”).

Możliwe jest utworzenie więcej niż jednej funkcji zamknięcia przez zwrócenie ich listy lub ustawienie ich na zmienne globalne. Wszystkie będą odnosić się do tego samego x i tego samego tmp, nie robią własnych kopii.

Tutaj liczba x jest liczbą dosłowną. Podobnie jak w przypadku innych literałów w języku JavaScript, gdy wywoływany jest kod foo, liczba x jest kopiowana na foo jako argument x.

Z drugiej strony, JavaScript zawsze używa odniesień podczas pracy z obiektami. Jeśli powiesz, wywołałeś foo za pomocą obiektu, zamknięcie, które zwróci, spowoduje odniesienie do oryginalnego obiektu!

 
function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Zgodnie z oczekiwaniami każde wywołanie do bar(10) zwiększy x.memb. Nie można oczekiwać, że x po prostu odnosi się do tego samego obiektu co zmienna age! Po kilku połączeniach do bar, age.memb będzie 2! To odniesienie jest podstawą wycieków pamięci z obiektami HTML.

    
3907
2019-05-22 12: 58: 05Z
  1. @ feeela: Tak, każda funkcja JS tworzy zamknięcie. Zmienne, do których nie ma odniesień, prawdopodobnie będą mogły zostać wyrzucone do śmieci w nowoczesnych silnikach JS, ale nie zmienia to faktu, że podczas tworzenia kontekstu wykonania ten kontekst ma odniesienie do otaczającego kontekstu wykonawczego i jego zmiennych oraz ta funkcja jest obiektem, który może zostać przeniesiony do innego zakresu zmiennych, zachowując pierwotne odniesienie. To jest zamknięcie.
    2013-08-19 01: 31: 25Z
  2. @ Ali Właśnie odkryłem, że jsFiddle, którego dostarczyłem, nie dowodzi niczego, ponieważ delete zawodzi. Niemniej jednak środowisko leksykalne, które funkcja będzie przenosić jako [[Zakres]] (i ostatecznie użyte jako baza dla własnego środowiska leksykalnego po wywołaniu) jest określane, gdy instrukcja definiująca funkcję jest wykonywana. Oznacza to, że funkcja jest zamykająca się nad CAŁKOWITĄ zawartością zakresu wykonawczego, bez względu na to, do jakich wartości faktycznie się odnosi i czy ucieka przed zakresem. Zobacz sekcje 13.2 i 10 w specyfikacji
    2013-08-20 17: 51: 46Z
  3. To była dobra odpowiedź, dopóki nie spróbowała wyjaśnić prymitywnych typów i odniesień. Robi się to całkowicie źle i mówi o kopiowaniu literałów, co naprawdę nie ma z tym nic wspólnego.
    2014-07-04 14: 53: 35Z
  4. Zamknięcia są odpowiedzią JavaScript na programowanie obiektowe oparte na klasach. JS nie jest oparty na klasach, więc trzeba było znaleźć inny sposób na zaimplementowanie niektórych rzeczy, których nie można zaimplementować inaczej.
    2014-09-18 10: 45: 56Z
  5. powinna to być zaakceptowana odpowiedź. Magia nigdy nie zdarza się w wewnętrznej funkcji. Dzieje się tak, gdy przypisujemy funkcję zewnętrzną do zmiennej. Tworzy to nowy kontekst wykonania dla funkcji wewnętrznej, więc „zmienna prywatna” może być gromadzona. Oczywiście, ponieważ zmienna przypisana do funkcji zewnętrznej zachowała kontekst. Pierwsza odpowiedź sprawia, że ​​całość jest bardziej złożona, nie wyjaśniając, co tak naprawdę się tam dzieje.
    2016-08-18 00: 26: 28Z

PRZEDMOWA: ta odpowiedź została napisana, gdy pytanie brzmiało:

  

Podobnie jak stary Albert powiedział: „Jeśli nie potrafisz wyjaśnić tego sześciolatkowi, sam go nie rozumiesz.” Cóż, próbowałem wyjaśnić zamknięcie JS 27-letniemu przyjacielowi i całkowicie nie powiodło się.

     

Czy ktoś może uznać, że mam 6 lat i dziwniezainteresowany tym tematem?

Jestem prawie pewien, że byłem jednym z niewielu ludzi, którzy próbowali dosłownie wziąć pierwsze pytanie. Od tego czasu pytanie wielokrotnie się zmutowało, więc moja odpowiedź może teraz wydawać się niesamowicie głupia i nie na miejscu. Mam nadzieję, że ogólna idea tej historii pozostanie dla niektórych zabawna.


Jestem wielkim fanem analogii i metafory podczas wyjaśniania trudnych pojęć, więc pozwólcie, że spróbuję swoich sił z historią.

Dawno, dawno temu:

Była księżniczka ...

 
function princess() {

Mieszkała w cudownym świecie pełnym przygód. Poznała swojego księcia z bajki, jeździła po jej świecie na jednorożcu, walczących smokach, napotkanych gadających zwierzętach i wielu innych fantastycznych rzeczach.

 
    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Ale zawsze będzie musiała wrócić do swojego nudnego świata obowiązków i dorosłych.

 
    return {

I często opowiadała im o swojej ostatniej niesamowitej przygodzie jako księżniczki.

 
        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Ale wszystko, co zobaczą, to mała dziewczynka ...

 
var littleGirl = princess();

... opowiadanie historii o magii i fantazji.

 
littleGirl.story();

I chociaż dorośli wiedzieli o prawdziwych księżniczkach, nigdy nie uwierzyliby w jednorożce ani smoki, ponieważ nigdy ich nie widziały. Dorośli powiedzieli, że istnieją tylko w wyobraźni dziewczynki.

Ale znamy prawdziwą prawdę; że dziewczynka z księżniczką w środku ...

... to naprawdę księżniczka z małą dziewczynką w środku.

    
2348
2017-11-01 11: 40: 09Z
  1. Naprawdę kocham to wyjaśnienie. Dla tych, którzy go czytają i nie podążają za nim, analogia jest następująca: funkcja princess () jest złożonym zakresem zawierającym prywatne dane. Poza funkcją nie można zobaczyć ani uzyskać dostępu do prywatnych danych. Księżniczka trzyma w wyobraźni jednorożce, smoki, przygody itp., A dorośli nie widzą ich dla siebie. ALE wyobraźnia księżniczki jest uchwycona w zamknięciu dla funkcji story(), która jest jedynym interfejsem, który instancja littleGirl eksponuje w świat magii.
    2013-02-28 07: 49: 20Z
  2. Więc tutaj story jest zamknięciem, ale kod miał var story = function() {}; return story;, a littleGirl byłoby zamknięciem. Przynajmniej takie wrażenie czerpię z użycia MDN w „prywatnym „metody z zamknięciami : „ Te trzy funkcje publiczne to zamknięcia, które mają to samo środowisko. ”
    2016-02-23 00: 58: 37Z
  3. @ icc97, tak, story to zamknięcie odwołujące się do środowiska udostępnionego w zakresie princess. princess to także inne domniemane zamknięcie, tj. princess i littleGirl będą dzielić dowolne odwołanie do tablicy parents, która istniałaby w środowisku /zakresie, w którym istnieje littleGirl i princess jest zdefiniowane.
    2016-03-01 16: 00: 04Z
  4. @ BenjaminKrupp Dodałem wyraźny komentarz do kodu, aby pokazać /sugerować, że w ciele jest więcej operacji niż princess. Niestety ta historia jest teraz nieco nie na miejscu w tym wątku. Pierwotnie pytanie wymagało „wyjaśnienia zamknięć JavaScript dla 5-letniego”; moja odpowiedź była jedyną, która nawet próbowała to zrobić. Nie wątpię, że zawiodłoby to nieszczęśliwie, ale przynajmniej ta odpowiedź mogłaby mieć szansę na zainteresowanie pięciolatka.
    2017-09-11 18: 45: 36Z
  5. Właściwie dla mnie to miało sens. I muszę przyznać, że zrozumienie zamknięcia JS dzięki opowieściom o księżniczkach i przygodach sprawia, że ​​czuję się trochę dziwnie.
    2017-10-02 10: 03: 34Z

Poważnie podchodząc do pytania, powinniśmy dowiedzieć się, co typowy sześciolatek jest zdolny do poznania, choć co prawda osoba zainteresowana JavaScriptem nie jest tak typowa.

Na Rozwoju dzieciństwa: od 5 do 7 lat mówi:

  

Twoje dziecko będzie mogło postępować zgodnie z dwuetapowymi wskazówkami. Na przykład, jeśli powiesz dziecku: „Idź do kuchni i przynieś mi kosz na śmieci”, będą mogli zapamiętać ten kierunek.

Możemy użyć tego przykładu do wyjaśnienia zamknięć w następujący sposób:

  

Kuchnia jest zamknięciem, które ma zmienną lokalną o nazwie trashBags. W kuchni jest funkcja getTrashBag, która pobiera jedną torbę na śmieci i zwraca ją.

Możemy kodować to w JavaScript tak:

 
function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Dalsze punkty wyjaśniające, dlaczego zamknięcia są interesujące:

  • Za każdym razem, gdy wywoływane jest makeKitchen(), tworzone jest nowe zamknięcie z własnym oddzielnym trashBags.
  • Zmienna trashBags jest lokalna do wnętrza każdej kuchni i nie jest dostępna na zewnątrz, ale wewnętrzna funkcja w obiekcie getTrashBag ma do niej dostęp.
  • Każde wywołanie funkcji tworzy zamknięcie, ale nie ma potrzeby utrzymywania zamknięcia, chyba że wewnętrzna funkcja, która ma dostęp do wnętrza zamknięcia, może zostać wywołana spoza zamknięcia. Zwracanie obiektu za pomocą funkcji getTrashBag robi to tutaj.
722
2018-10-10 17: 50: 14Z
  1. Właściwie, funkcja makeKitchen call to rzeczywiste zamknięcie, a nie obiekt kuchenny, który zwraca.
    2016-06-27 17: 56: 00Z
  2. Po przejściu przez innych znalazłem tę odpowiedź jako najłatwiejszy sposób na wyjaśnienie, co i dlaczego closures.is.
    2016-08-12 15: 12: 01Z
  3. Zbyt dużo menu i przekąsek, za mało mięsa i ziemniaków. Możesz poprawić tę odpowiedź za pomocą jednego krótkiego zdania, takiego jak: „Zamknięcie jest zamkniętym kontekstem funkcji, z powodu braku jakiegokolwiek mechanizmu określania zakresu zapewnianego przez klasy.”
    2017-05-13 16: 30: 28Z

Słomiany człowiek

Muszę wiedzieć, ile razy przycisk został kliknięty i zrobić coś przy co trzecim kliknięciu ...

Dość oczywiste rozwiązanie

 
// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Teraz to zadziała, ale wkracza w zakres zewnętrzny, dodając zmienną, której jedynym celem jest śledzenie liczby. W niektórych sytuacjach byłoby to lepsze, ponieważ aplikacja zewnętrzna może potrzebować dostępu do tych informacji. Ale w tym przypadku zmieniamy tylko zachowanie każdego trzeciego kliknięcia, dlatego lepiej jest włączyć tę funkcjonalność do obsługi zdarzeń .

Rozważ tę opcję

 
var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Zwróć uwagę na kilka rzeczy tutaj.

W powyższym przykładzie używam zachowania zamknięcia JavaScript. To zachowanie pozwala każdej funkcji na dostęp do zakresu, w którym została utworzona, na czas nieokreślony. Aby praktycznie to zastosować, natychmiast wywołuję funkcję, która zwraca inną funkcję, a ponieważ funkcja, którą zwracam ma dostęp do zmiennej zliczania wewnętrznego (ze względu na zachowanie zamknięcia wyjaśnione powyżej) skutkuje prywatnym zakresem do wykorzystania przez wynikową funkcję ... Nie takie proste? Rozcieńczmy to ...

Proste jednokreskowe zamknięcie

 
//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Wszystkie zmienne spoza zwracanej funkcji są dostępne dla zwróconej funkcji, ale nie są bezpośrednio dostępne dla zwróconego obiektu funkcji ...

 
func();  // Alerts "val"
func.a;  // Undefined

Pobierz? W naszym pierwotnym przykładzie zmienna count jest zawarta w zamknięciu i zawsze dostępna dla obsługi zdarzeń, dzięki czemu zachowuje swój stan od kliknięcia do kliknięcia.

Ponadto ten stan zmiennej prywatnej jest w pełni dostępny, zarówno dla odczytów, jak i przypisanych do prywatnych zmiennych zasięgu.

Idź; w pełni zamykasz to zachowanie.

Pełny wpis na blogu (w tym jQuery uwagi)

    
560
2017-11-13 04: 54: 05Z
  1. Nie zgadzam się z twoją definicją zamknięcia. Nie ma powodu, żeby się odwoływać. Jest również nieco uproszczone (i niedokładne) stwierdzenie, że musi zostać „zwrócone” (wiele dyskusji na ten temat w komentarzach najwyższej odpowiedzi na to pytanie)
    2013-02-26 19: 51: 22Z
  2. @ James, nawet jeśli sobie tego życzysz, jego przykład (i cały post) jest jednym z najlepszych, jakie widziałem. Chociaż pytanie nie jest dla mnie stare i rozwiązane, zasługuje na +1.
    2013-02-27 11: 20: 09Z
  3. "Muszę wiedzieć, ile razy kliknięto przycisk i zrobić coś na co trzecim kliknięciu ..." TO dostało moje Uwaga. Przypadek użycia i rozwiązanie pokazujące, w jaki sposób zamknięcie nie jest tak tajemniczą sprawą i że wielu z nas je pisało, ale nie znało oficjalnej nazwy.
    2014-01-10 13: 49: 37Z
  4. Ładny przykład, ponieważ pokazuje, że „count” w drugim przykładzie zachowuje wartość „count” i nie resetuje się do 0 przy każdym kliknięciu „elementu”. Bardzo pouczające!
    2014-07-21 06: 19: 54Z
  5. + 1 dla zachowania zamknięcia . Czy możemy ograniczyć zachowanie zamknięcia do funkcji w javascript lub ta koncepcja może być również zastosowana do innych struktur języka?
    2015-03-08 19: 32: 34Z

Zamknięcia są trudne do wytłumaczenia, ponieważ są używane do tego, aby niektóre zachowania działały tak, jak i tak każdy intuicyjnie oczekuje. Uważam, że najlepszym sposobem na ich wyjaśnienie (i sposobem, w jaki dowiedziałem się, co robią) jest wyobrażenie sobie sytuacji bez nich:

 
    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

Co by się stało, gdyby JavaScript nie znał zamknięć? Wystarczy zastąpić wywołanie w ostatnim wierszu jego treścią (co w zasadzie oznacza wywołanie funkcji), a otrzymasz:

 
console.log(x + 3);

Gdzie jest definicja x? Nie zdefiniowaliśmy go w bieżącym zakresie. Jedynym rozwiązaniem jest pozwolić plus5 przenosić jego zakres (a raczej zakres jego rodzica) dookoła. W ten sposób x jest dobrze zdefiniowany i jest powiązany z wartością 5.

    
472
2017-03-17 08: 51: 15Z
  1. Zgadzam się. Nadawanie funkcjom znaczących nazw zamiast tradycyjnych „foobar” również bardzo mi pomaga. Semantyka się liczy.
    2010-04-08 14: 16: 40Z
  2. więc w pseudojęzyku jest to w zasadzie alert(x+3, where x = 5). where x = 5 to zamknięcie. Czy mam rację?
    2010-12-22 09: 52: 33Z
  3. @ Jus12: dokładnie. Za kulisami zamknięcie to tylko miejsce, w którym przechowywane są bieżące wartości zmiennych („wiązania”), jak w przykładzie.
    2010-12-22 11: 28: 09Z
  4. To jest dokładnie taki przykład, który wprowadza wielu ludzi w błąd, że wartości są używane w zwróconej funkcji, a nie sama zmienna zmienna. Gdyby została zmieniona na „zwracaj x + = y”, a jeszcze lepiej zarówno na tę, jak i na inną funkcję „x * = y”, to byłoby jasne, że nic nie jest kopiowane. Dla ludzi, którzy stosują ramki, wyobraź sobie, że używasz ramek sterty, które mogą nadal istnieć po zwróceniu funkcji.
    2013-06-21 12: 36: 49Z
  5. @ Matt I nie zgadzam się. Przykładem jest nie , który ma wyczerpująco dokumentować wszystkie właściwości. To jest przeznaczonebądź redukcyjny i zilustruj istotną cechę pojęcia. OP poprosił o proste wyjaśnienie („dla sześciolatka”). Przyjmij zaakceptowaną odpowiedź: całkowicie zawiedzie w dostarczeniu zwięzłego wyjaśnienia, właśnie dlatego, że stara się być wyczerpująca. (Zgadzam się z Tobą, że ważną cechą JavaScript jest to, że wiązanie jest raczej przez odniesienie niż wartość ... ale znowu, udane wyjaśnienie to takie, które sprowadza się do absolutnego minimum.)
    2013-06-21 14: 30: 42Z

Jest to próba wyjaśnienia kilku (możliwych) nieporozumień dotyczących zamknięć, które pojawiają się w niektórych innych odpowiedziach.

  • Zamknięcie nie jest tworzone tylko wtedy, gdy zwracasz funkcję wewnętrzną. W rzeczywistości, funkcja zamykająca nie musi wcale wracać , aby można było utworzyć zamknięcie. Zamiast tego możesz przypisać funkcję wewnętrzną do zmiennej w zewnętrznym zakresie lub przekazać ją jako argument innej funkcji, w której można ją wywołać natychmiast lub w dowolnym momencie później. Dlatego zamknięcie otaczającej funkcji jest prawdopodobnie tworzone , gdy tylko funkcja obejmująca zostanie wywołana , ponieważ każda funkcja wewnętrzna ma dostęp do tego zamknięcia za każdym razem, gdy wywoływana jest funkcja wewnętrzna, przed lub po zwróceniu funkcji zamykającej.
  • Zamknięcie nie odwołuje się do kopii starych wartości zmiennych w swoim zakresie. Same zmienne są częścią zamknięcia, a więc wartość widoczna podczas uzyskiwania dostępu do jednego z nich zmienne to najnowsza wartość w momencie uzyskiwania dostępu. Dlatego wewnętrzne funkcje tworzone wewnątrz pętli mogą być trudne, ponieważ każdy z nich ma dostęp do tych samych zmiennych zewnętrznych niż do chwytania kopii zmiennych w czasie tworzenia lub wywoływania funkcji.
  • „Zmienne” w zamknięciu obejmują dowolne nazwane funkcje zadeklarowane w funkcji. Zawierają również argumenty funkcji. Zamknięcie ma również dostęp do zawartych w nim zmiennych zamknięcia, aż do zasięgu globalnego.
  • Zamknięcia używają pamięci, ale nie powodują przecieków pamięci , ponieważ sam JavaScript czyści swoje własne struktury kołowe, do których nie ma odniesienia. Przecieki pamięci programu Internet Explorer związane z zamknięciami są tworzone, gdy nie można odłączyć wartości atrybutów DOM, które odnoszą się do zamknięć, zachowując w ten sposób odniesienia do możliwie okrągłych struktur.
359
2016-05-05 15: 00: 49Z
  1. Przy okazji, dodałem tę "odpowiedź" z wyjaśnieniami, aby nie odnosić się bezpośrednio do pierwotnego pytania. Zamiast tego mam nadzieję, że każda prosta odpowiedź (dla 6-latka) nie wprowadzi błędnych pojęć dotyczących tego złożonego tematu. Na przykład. powyższa popularna odpowiedź wiki mówi: „Zamknięcie następuje po zwróceniu funkcji wewnętrznej”. Poza błędem gramatycznym, jest to technicznie złe.
    2011-07-21 14: 15: 29Z
  2. James, powiedziałem, że zamknięcie jest "prawdopodobnie" utworzone w momencie wywołania funkcji obejmującej, ponieważ jest prawdopodobne, że implementacja może opóźnić utworzenie zamknięcia do pewnego czasu, kiedy zdecyduje, że zamknięcie jest absolutnie konieczne. Jeśli w funkcji obejmującej nie zdefiniowano żadnej funkcji wewnętrznej, nie będzie konieczne zamknięcie. Może więc może poczekać, aż zostanie utworzona pierwsza funkcja wewnętrzna, aby utworzyć zamknięcie kontekstu wywołania funkcji otaczającej.
    2012-07-10 17: 27: 03Z
  3. @ Beetroot-Beetroot Załóżmy, że mamy wewnętrzną funkcję, która jest przekazywana do innej funkcji, gdzie jest używana przed funkcja zewnętrzna zwraca i przypuśćmy zwracamy również tę samą funkcję wewnętrzną z funkcji zewnętrznej. Jest to identyczna funkcja w obu przypadkach, ale mówisz, że zanim funkcja zewnętrzna powróci, funkcja wewnętrzna jest „powiązana” ze stosem wywołań, podczas gdy po powrocie funkcja wewnętrzna zostaje nagle powiązana z zamknięciem. Zachowuje się identycznie w obu przypadkach; semantyka jest identyczna, więc nie mówisz tylko o szczegółach implementacji?
    2012-10-16 16: 06: 13Z
  4. @ Beetroot-Beetroot, dzięki za feedback i cieszę się, że myślałem. Nadal nie widzę żadnej różnicy semantycznej między kontekstem na żywo funkcji zewnętrznej a tym samym kontekstem, gdy staje się ona zamknięciem w miarę powrotu funkcji (jeśli rozumiem twoją definicję). Wewnętrzna funkcja nie ma znaczenia. Zbieranie śmieci nie ma znaczenia, ponieważ wewnętrzna funkcja utrzymuje odniesienie do kontekstu /zamknięcia w dowolny sposób, a wywołujący funkcję zewnętrzną po prostu odwołuje się do kontekstu wywołania. Ale jest to mylące dla ludzi i może lepiej nazwać to kontekstem wywołania.
    2012-10-18 00: 26: 54Z
  5. Ten artykuł jest trudny do odczytania, ale myślę, że w rzeczywistości obsługuje to, co mówię. Mówi: „Zamknięcie tworzy się przez zwrócenie obiektu funkcji [...] lub przez bezpośrednie przypisanie odniesienia do takiego obiektu funkcji, na przykład zmiennej globalnej.” Nie mam na myśli, że GC nie ma znaczenia. Raczej, z powodu GC, a ponieważ funkcja wewnętrzna jest dołączona do kontekstu wywołania funkcji zewnętrznej (lub [[scope]], jak mówi artykuł), to nie ma znaczenia, czy wywołanie funkcji zewnętrznej zwraca, ponieważ wiązanie z wewnętrznym funkcja jest ważna.
    2012-10-21 01: 49: 59Z

OK, 6-letni wentylator zamykający. Czy chcesz usłyszeć najprostszy przykład zamknięcia?

Wyobraźmy sobie następną sytuację: kierowca siedzi w samochodzie. Ten samochód jest w samolocie. Samolot jest na lotnisku. Zdolność kierowcy do dostępu do rzeczy poza jego samochodem, ale wewnątrz samolotu, nawet jeśli ten samolot opuszcza lotnisko, jest zamknięciem. to jest to! Po ukończeniu 27 lat spójrz na bardziej szczegółowe wyjaśnienie lub na poniższy przykład.

Oto jak mogę przekonwertować historię swojego samolotu na kod.

 
var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");
    
357
2018-10-10 18: 38: 01Z
  1. Dobrze zagrany i odpowiada na oryginalny plakat. Myślę, że to najlepsza odpowiedź. Miałem zamiar użyć bagażu w podobny sposób: wyobraź sobie, że idziesz do domu babci i pakujesz swoją walizkę z Nintendo DS w karty do gry w swoim futerale, ale potem pakujesz walizkę do plecaka, a także wkładasz karty do gry do kieszeni plecaka, i Następnie wkładasz całość do dużej walizki z większą ilością kart do gry w kieszeniach walizki. Gdy dotrzesz do domu babci, możesz zagrać w dowolną grę na swoim DS, o ile wszystkie zewnętrzne sprawy są otwarte. lub coś takiego.
    2013-09-19 00: 37: 32Z

Zamknięcie przypomina obiekt. Uruchamia się za każdym razem, gdy wywołujesz funkcję.

Zakres zamknięcia w JavaScript jest leksykalny, co oznacza, że ​​wszystko, co jest zawarte w funkcji, do której należy zamknięcie , ma dostęp do dowolnej zmiennej, która znajduje się w to.

Zmienna jest zawarta w zamknięciu , jeśli

  1. przypisz go za pomocą var foo=1; lub
  2. po prostu napisz var foo;

Jeśli funkcja wewnętrzna (funkcja zawarta w innej funkcji) uzyskuje dostęp do takiej zmiennej bez definiowania jej we własnym zakresie za pomocą var, modyfikuje zawartość zmiennej w zewnętrznym zamknięciu . p>

Zamknięcie przeżywa czas działania funkcji, która go zrodziła. Jeśli inne funkcje usuwają je z zamknięcia /zakresu , w którym są zdefiniowane (na przykład jako wartości zwracane), będą one nadal odwoływać się do zamknięcia .

Przykład

 
function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

Wyjście

 
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged
    
352
2018-10-10 18: 39: 20Z
  1. Wow, nigdy nie wiedziałem, że możesz użyć podstawień ciągu w console.log w ten sposób. Jeśli ktoś jest zainteresowany, jest ich więcej: developer.mozilla.org/en-US/docs/DOM/…
    2013-01-04 02: 59: 32Z
  2. Zmienne znajdujące się na liście parametrów funkcji są również częścią zamknięcia (np. nie tylko ograniczone do var).
    2015-03-18 03: 38: 09Z
  3. Zamknięcia brzmią bardziej jak obiekty i klasy itp. Nie wiesz, dlaczego wiele osób nie porównuje tych dwóch - łatwiej byłoby nam się uczyć nowicjuszy! >
    2019-05-23 12: 37: 10Z

    Napisałem post na blogu, wyjaśniając zamknięcie. Oto, co powiedziałem o zamknięciach w zakresie dlaczego chcesz go mieć.

      

    Zamknięcia to sposób na pozwolenie funkcji   masz trwałe, prywatne zmienne -   to znaczy zmienne, które tylko jedna   funkcja wie o tym, gdzie może   śledzić informacje z poprzednich czasów   że został uruchomiony.

    W tym sensie pozwalają funkcji działać trochę jak obiekt z prywatnymi atrybutami.

    Pełny post:

    Co to za rzeczy do zamknięcia?

        
    230
    2013-01-28 02: 23: 21Z
    1. Czy główna korzyść z zamknięcia może być podkreślona w tym przykładzie? Powiedz, że mam funkcję emailError (sendToAddress, errorString), a potem mógłbym powiedzieć devError = emailError("devinrhode2@googmail.com", errorString), a następnie mieć własną, niestandardową wersję współdzielonej funkcji e-mailowej?
      2011-07-31 06: 42: 49Z
    2. Po przerzuceniu się przez całą masę 'splaingu', zacząłem wreszcie rozumieć, po co są. Pomyślałem sobie: „Och, to jak prywatne zmienne w obiekcie?” i bam. To była kolejna odpowiedź, którą przeczytałem.
      2012-12-30 21: 20: 07Z
    3. To wyjaśnienie i związany z nim doskonały przykład w linku do (zamknięcie rzeczy) to najlepszy sposób na zrozumienie zamknięć i powinien być na samym szczycie!
      2019-05-05 09: 45: 39Z

    Zamknięcia są proste:

    Poniższy prosty przykład obejmuje wszystkie główne punkty zamknięć JavaScript. *

    Oto fabryka, która produkuje kalkulatory, które mogą dodawać i mnożyć:

     
    function make_calculator() {
      var n = 0; // this calculator stores a single number n
      return {
        add: function(a) {
          n += a;
          return n;
        },
        multiply: function(a) {
          n *= a;
          return n;
        }
      };
    }
    
    first_calculator = make_calculator();
    second_calculator = make_calculator();
    
    first_calculator.add(3); // returns 3
    second_calculator.add(400); // returns 400
    
    first_calculator.multiply(11); // returns 33
    second_calculator.multiply(10); // returns 4000
    

    Kluczowy punkt: każde wywołanie make_calculator tworzy nową zmienną lokalną n, która nadal jest użyteczna dla funkcji tego kalkulatora add i multiply długo po make_calculator zwrotach.

    Jeśli znasz ramki stosu, te kalkulatory wydają się dziwne: jak mogą uzyskać dostęp do n po make_calculator zwrotach? Odpowiedź brzmi: wyobraź sobie, że JavaScript nie używa „klatek stosu”, ale zamiast tego używa „ramek sterty”, które mogą się utrzymywać po wywołaniu funkcji, które spowodowało ich powrót.

    Funkcje wewnętrzne, takie jak add i multiply, które uzyskują dostęp do zmiennych zadeklarowanych w zewnętrznej funkcji ** , nazywają się zamknięciami .

    To wszystko polega na zamknięciu.



    * Na przykład obejmuje wszystkie punkty w artykule „Closures for Dummies” podanym w inna odpowiedź , z wyjątkiem przykładu 6, który po prostu pokazuje, że zmienne mogą być użyte przed ich zadeklarowaniem, fajny fakt do poznania, ale całkowicie niezwiązany z zamknięciami. Obejmuje również wszystkie punkty w zaakceptowanej odpowiedzi , z wyjątkiem punktów (1), które funkcje kopiują swoje argumenty do zmiennych lokalnych (nazwane argumenty funkcji) i (2), że kopiowanie liczb tworzy nowy numer, ale kopiowanie odwołania do obiektu daje kolejne odwołanie do tego samego obiektu. Te są również dobre do poznania, ale znowu całkowicie niezwiązane z zamknięciami. jat jest bardzo podobny do przykładu w tej odpowiedzi , ale jest nieco krótszy i mniej abstrakcyjny. Nie obejmuje punktu tej odpowiedzi lub ten komentarz , co oznacza, że ​​JavaScript utrudnia podłączenie bieżącej wartości zmiennej pętli w twoją wewnętrzną funkcję: Krok „podłączanie” można wykonać tylko za pomocą funkcji pomocniczej, która zamyka twoją wewnętrzną funkcję i jest wywoływana przy każdej iteracji pętli. (Ściśle rzecz biorąc, funkcja wewnętrzna uzyskuje dostęp do kopii zmiennej funkcji pomocniczej, zamiast mieć cokolwiek podłączonego.) Ponownie, bardzo przydatna podczas tworzenia zamknięć, ale nie część tego, czym jest zamknięcie lub jak działa. Istnieje dodatkowe zamieszanie spowodowane zamknięciami działającymi inaczej w językach funkcjonalnych, takich jak ML, gdzie zmienne są związane raczej z wartościami niż z przestrzenią pamięci, zapewniając stały strumień ludzi, którzy rozumieją zamknięcia w pewien sposób (mianowicie „podłączanie” w sposób). po prostu niepoprawny dla JavaScript, gdzie zmienne są zawsze związane z przestrzenią pamięci i nigdy do wartości.

    ** Każda zewnętrzna funkcja, jeśli kilka jest zagnieżdżonych lub nawet w kontekście globalnym, jako 210

    2017-05-23 12: 10: 46Z
    1. Co by się stało, gdybyś zadzwonił: second_calculator = first_calculator (); zamiast second_calculator = make_calculator (); ? Powinno być takie samo, prawda?
      2016-10-07 05: 02: 17Z
    2. @ Ronen: Ponieważ first_calculator jest obiektem (nie funkcją) nie powinieneś używać nawiasów w second_calculator = first_calculator;, ponieważ jest to przypisanie, a nie wywołanie funkcji. Aby odpowiedzieć na twoje pytanie, byłoby wtedy tylko jedno wywołanie make_calculator, więc powstałby tylko jeden kalkulator, a zmienne first_calculator i second_calculator odnosiłyby się do tego samego kalkulatora, więc odpowiedzi wynosiłyby 3, 403, 4433, 44330.
      2016-10-10 09: 49: 48Z

    Jak wytłumaczyłbym to sześciolatkowi:

    Wiesz, jak dorośli mogą posiadać dom, i nazywają go domem? Kiedy mama ma dziecko, dziecko tak naprawdę niczego nie posiada, prawda? Ale rodzice są właścicielami domu, więc kiedy ktoś pyta dziecko „Gdzie jest twój dom?”, Może odpowiedzieć „ten dom!” I wskazać dom jego rodziców. „Zamknięcie” to zdolność dziecka do tego, aby zawsze (nawet jeśli za granicą) było w stanie powiedzieć, że ma dom, nawet jeśli to dom jest własnością rodziców.

        
    203
    2016-01-16 02: 30: 44Z

    Czy możesz wyjaśnić zamknięcia do 5-latek? *

    Nadal uważam, że wyjaśnienie Google działa bardzo dobrze i jest zwięzły:

     
    /*
    *    When a function is defined in another function and it
    *    has access to the outer function's context even after
    *    the outer function returns.
    *
    * An important concept to learn in JavaScript.
    */
    
    function outerFunction(someNum) {
        var someString = 'Hey!';
        var content = document.getElementById('content');
        function innerFunction() {
            content.innerHTML = someNum + ': ' + someString;
            content = null; // Internet Explorer memory leak for DOM reference
        }
        innerFunction();
    }
    
    outerFunction(1);​
    

    Dowód, że ten przykład tworzy zamknięcie, nawet jeśli funkcja wewnętrzna nie wraca

    * A C # pytanie

        
    195
    2017-05-23 11: 47: 32Z
    1. Jeśli przeczytasz opis, zobaczysz, że twój przykład jest niepoprawny. Wywołanie funkcji innerFunction mieści się w zakresie funkcji zewnętrznej, a nie, jak mówi opis, po zwróceniu funkcji zewnętrznej. Kiedykolwiek wywołujesz outerFunction, tworzona jest nowa wewnętrzna funkcja, a następnie używana w zakresie.
      2010-12-06 16: 11: 06Z
    2. @ Moss to nie moje komentarze, są develem Googleoper's
      2010-12-06 23: 09: 14Z
    3. Widząc, że innerFunction nie jest przywoływany poza zakresem outerFunction, czy interpreter jest wystarczająco inteligentny, aby zobaczyć, że nie wymaga zamknięcia?
      2011-03-07 05: 49: 31Z
    4. Kod jest „poprawny”, jako przykład zamknięcia, mimo że nie odnosi się do części komentarza o używaniu zamknięcia po zwróceniu funkcji outerFunction. Więc to nie jest świetny przykład. Istnieje wiele innych sposobów na zamknięcie, które nie wymagają zwrotu wewnętrznej funkcji. na przykład innerFunction może zostać przekazany do innej funkcji, gdzie jest wywoływany natychmiast lub przechowywany i wywoływany jakiś czas później, i we wszystkich przypadkach ma dostęp do kontekstu outerFunction, który został utworzony, gdy został wywołany.
      2011-08-04 14: 01: 11Z
    5. @ syockit Nie, Moss się myli. Zamknięcie jest tworzone niezależnie od tego, czy funkcja kiedykolwiek wymyka się zakresowi, w którym została zdefiniowana, a bezwarunkowo utworzone odwołanie do środowiska leksykalnego rodzica sprawia, że ​​wszystkie zmienne w zakresie macierzystym są dostępne dla wszystkich funkcji, niezależnie od czy są wywoływane na zewnątrz lub wewnątrz zakresu, w którym zostały utworzone.
      2013-08-21 13: 41: 18Z

    Zazwyczaj lepiej się uczę dzięki porównaniom GOOD /BAD. Lubię zobaczyć działający kod, po którym następuje niepracujący kod, który ktoś może napotkać. Złożyłem a jsFiddle , który porównuje i próbuje sprowadzić różnice do najprostszych wyjaśnień, które mogłem wymyślić z.

    Zamknięcia wykonane poprawnie:

     
    console.log('CLOSURES DONE RIGHT');
    
    var arr = [];
    
    function createClosure(n) {
        return function () {
            return 'n = ' + n;
        }
    }
    
    for (var index = 0; index < 10; index++) {
        arr[index] = createClosure(index);
    }
    
    for (var index in arr) {
        console.log(arr[index]());
    }
    
    • W powyższym kodzie createClosure(n) jest wywoływany w każdej iteracji pętli. Zauważ, że zmienną n nazwałam, aby podkreślić, że jest to nowa zmienna utworzona w nowym zakresie funkcji i nie jest tą samą zmienną co index, która jest powiązana z zewnętrznym zakresem.

    • Tworzy nowy zakres, a n jest powiązany z tym zakresem; Oznacza to, że mamy 10 oddzielnych zakresów, po jednym dla każdej iteracji.

    • createClosure(n) zwraca funkcję, która zwraca nw tym zakresie.

    • W obrębie każdego zakresu n jest powiązany z dowolną wartością, jaką miał, gdy createClosure(n) został wywołany, więc zagnieżdżona funkcja, która zostanie zwrócona, zawsze zwróci wartość n, którą miała, gdy createClosure(n) zostało wywołane.

    Zamknięcia wykonane źle:

     
    console.log('CLOSURES DONE WRONG');
    
    function createClosureArray() {
        var badArr = [];
    
        for (var index = 0; index < 10; index++) {
            badArr[index] = function () {
                return 'n = ' + index;
            };
        }
        return badArr;
    }
    
    var badArr = createClosureArray();
    
    for (var index in badArr) {
        console.log(badArr[index]());
    }
    
    • W powyższym kodzie pętla została przeniesiona w ramach funkcji createClosureArray(), a funkcja teraz zwraca właśnie ukończoną tablicę, która na pierwszy rzut oka wydaje się bardziej intuicyjna.

    • To, co może nie być oczywiste, to fakt, że ponieważ createClosureArray() jest wywoływany tylko wtedy, gdy dla tej funkcji tworzony jest tylko jeden zakres, zamiast jednego dla każdej iteracji pętli.

    • W tej funkcji zdefiniowana jest zmienna o nazwie index. Pętla działa i dodaje funkcje do tablicy, która zwraca index. Zauważ, że index jest zdefiniowane w funkcji createClosureArray, która jest wywoływana tylko raz.

    • Ponieważ w funkcji createClosureArray() był tylko jeden zakres, index jest powiązany tylko z wartością w tym zakresie. Innymi słowy, za każdym razem, gdy pętla zmienia wartość index, zmienia ją na wszystko, co odnosi się do niej w tym zakresie.

    • Wszystkie funkcje dodane do tablicy zwracają zmienną SAME index z zakresu macierzystego, gdzie została zdefiniowana, zamiast 10 różnych z 10 różnych zakresów, takich jak pierwszy przykład. W rezultacie wszystkie 10 funkcji zwraca tę samą zmienną z tego samego zakresu.

    • Po zakończeniu pętli i modyfikacji index wartość końcowa wynosiła 10, dlatego każda funkcja dodana do tablicy zwraca wartość pojedynczej zmiennej index, która jest teraz ustawiona na 10.

    Wynik

      

    ZAMKNIĘTE PRAWO
      n = 0
      n = 1
      n = 2
      n = 3
      n = 4
      n = 5
      n = 6
      n = 7
      n = 8
      n = 9

         

    ZAMKNIĘTE ŹLE
      n = 10
      n = 10
      n = 10
      n = 10
      n = 10
      n = 10
      n = 10
      n = 10
      n = 10
      n = 10

        
    169
    2017-03-27 17: 56: 11Z
      Miły dodatek, dzięki. Aby wyjaśnić, można sobie wyobrazić, w jaki sposób „zła” tablica jest tworzona w „złej” pętli przy każdej iteracji: 1. iteracja: [function () {return 'n =' + 0;}] 2. iteracja: [( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] 3. iteracja: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] itd. Tak więc za każdym razem, gdy zmienia się wartość indeksu, znajduje to odzwierciedlenie we wszystkich funkcjach już dodane do tablicy.
      2016-04-08 22: 38: 08Z
    1. Używanie let do var naprawia różnicę.
      2017-10-16 10: 25: 19Z
    2. Czy nie jest to tutaj „zamknięcie zrobione dobrze” jest przykładem „zamknięcia wewnątrz zamknięcia”?
      2017-12-29 10: 17: 01Z
    3. Mam na myśli, że każda funkcja jest technicznie zamknięciem, ale ważną częścią jest to, że funkcja definiuje nową zmienną wewnątrz. Funkcja zwracająca tylko referencje n utworzone w nowym zamknięciu. Zwracamy po prostu funkcję, abyśmy mogli ją zapisać w tablicy i wywołać ją później.
      2017-12-29 18: 52: 58Z
    4. Jeśli chcesz po prostu zapisać wynik w tablicy w pierwszej iteracji, możesz wpisać go w następujący sposób: arr[index] = (function (n) { return 'n = ' + n; })(index);. Ale następnie zapisujesz wynikowy ciąg znaków w tablicy, a nie funkcję, która ma wywołać, co podważa punkt mojego przykładu.
      2017-12-29 18: 54: 55Z

    Wikipedia na zamknięciach :

      

    W informatyce zamknięcie jest funkcją wraz ze środowiskiem odniesienia dla nielokalnych nazw (wolnych zmiennych) tej funkcji.

    Technicznie rzecz biorąc, w JavaScript , każda funkcja jest zamknięciem . Zawsze ma dostęp do zmiennych zdefiniowanych w otaczającym zakresie.

    Ponieważ konstrukcja definiująca zakres w JavaScript jest funkcją , a nie blokiem kodu jak w wielu innych językach, co zwykle oznacza zamknięcie w JavaScript to funkcja działająca z nielokalnymi zmiennymi zdefiniowanymi w już wykonanej funkcji otoczenia .

    Zamknięcia są często używane do tworzenia funkcji z ukrytymi prywatnymi danymi (ale nie zawsze tak jest).

     
    var db = (function() {
        // Create a hidden object, which will hold the data
        // it's inaccessible from the outside.
        var data = {};
    
        // Make a function, which will provide some access to the data.
        return function(key, val) {
            if (val === undefined) { return data[key] } // Get
            else { return data[key] = val } // Set
        }
        // We are calling the anonymous surrounding function,
        // returning the above inner function, which is a closure.
    })();
    
    db('x')    // -> undefined
    db('x', 1) // Set x to 1
    db('x')    // -> 1
    // It's impossible to access the data object itself.
    // We are able to get or set individual it.
    

    ems

    Powyższy przykład używa funkcji anonimowej, która została wykonana raz. Ale tak nie musi być. Może być nazwany (np. mkdb) i wykonany później, generując funkcję bazy danych przy każdym wywołaniu. Każda wygenerowana funkcja będzie miała swój własny ukryty obiekt bazy danych. Innym przykładem użycia zamknięć jest sytuacja, w której nie zwracamy funkcji, ale obiekt zawierający wiele funkcji dla różnych celów, z których każda ma dostęp do tych samych danych.

        
    161
    2013-12-18 16: 48: 34Z
    1. To jest najlepsze wyjaśnienie zamykania JavaScript. Powinna być wybrana odpowiedź. Reszta jest wystarczająco zabawna, ale ta jest rzeczywiście przydatna w praktyczny sposób dla prawdziwych koderów JavaScript.
      2018-02-04 12: 32: 46Z

    Przygotowałem interaktywny samouczek JavaScript, aby wyjaśnić, jak działają zamknięcia. Co to jest zamknięcie?

    Oto jeden z przykładów:

     
    var create = function (x) {
        var f = function () {
            return x; // We can refer to x here!
        };
        return f;
    };
    // 'create' takes one argument, creates a function
    
    var g = create(42);
    // g is a function that takes no arguments now
    
    var y = g();
    // y is 42 here
    
        
    135
    2014-10-25 22: 38: 03Z
      

    Dzieci zawsze będą pamiętać tajemnice, które dzielili z rodzicami, nawet po przybyciu rodziców   odszedł. Oto, jakie są zamknięcia dla funkcji.

    Sekretami funkcji JavaScript są prywatne zmienne

     
    var parent = function() {
     var name = "Mary"; // secret
    }
    

    Za każdym razem, gdy ją wywołujesz, tworzona jest lokalna zmienna „name” o nazwie „Mary”. I za każdym razem, gdy funkcja wychodzi, zmienna jest tracona, a nazwa zostaje zapomniana.

    Jak można się domyślić, ponieważ zmienne są ponownie tworzone za każdym razem, gdy wywoływana jest funkcja, a nikt inny ich nie zna, musi istnieć tajne miejsce, w którym są przechowywane. Może to być Komnata Tajemnic lub stos lub zasięg lokalny , ale tak naprawdę nie ma to znaczenia. Wiemy, że są gdzieś ukryte w pamięci.

    Ale w JavaScript istnieje ta szczególna rzecz, którą funkcje tworzone w innych funkcjach mogą również znać lokalne zmienne swoich rodziców i przechowywać je tak długo, jak długo będą żyć.

     
    var parent = function() {
      var name = "Mary";
      var child = function(childName) {
        // I can also see that "name" is "Mary"
      }
    }
    

    Tak więc, dopóki jesteśmy w funkcji-rodzica, może utworzyć jedną lub więcej funkcji potomnych, które dzielą tajne zmienne z tajnego miejsca.

    Ale smutną rzeczą jest to, że jeśli dziecko jest także prywatną zmienną swojej funkcji nadrzędnej, to również umrze, gdy rodzic się skończy, a tajemnice zginą wraz z nimi.

    Aby żyć, dziecko musi odejść, zanim będzie za późno

     
    var parent = function() {
      var name = "Mary";
      var child = function(childName) {
        return "My name is " + childName  +", child of " + name; 
      }
      return child; // child leaves the parent ->
    }
    var child = parent(); // < - and here it is outside 
    

    A teraz, mimo że Maryja „już nie działa”, pamięć o niej nie ginie, a jej dziecko zawsze będzie pamiętać swoje imię i inne tajemnice, które dzieliły podczas wspólnego spędzania czasu.

    Więc, jeśli zadzwonisz do dziecka „Alice”, ona odpowie

     
    child("Alice") => "My name is Alice, child of Mary"
    

    To wszystko, co musisz powiedzieć.

        
    124
    2017-07-13 11: 27: 32Z
    1. To wyjaśnienie było dla mnie najbardziej sensowne, ponieważ nie zakłada znaczącej wcześniejszej znajomości terminów technicznych. Głosowane tutaj wyjaśnienie zakłada, że ​​osoba, która nie rozumie zamknięć, ma pełne i pełne zrozumienie takich pojęć, jak „zakres leksykalny” i „kontekst egzekucji” - chociaż rozumiem je pojęciowo, nie sądzę, że jestem tak czuję się komfortowo ze szczegółami, tak jak powinienem, a wyjaśnienie bez żargonu w ogóle jest tym, co sprawiło, że zamknięcia w końcu kliknęły, dziękuję. Jako bonus, myślę, że wyjaśnia również, jaki zakres jest bardzo zwięzły.
      2015-05-17 20: 30: 27Z

    Nie rozumiem, dlaczego odpowiedzi są tutaj tak złożone.

    Oto zamknięcie:

     
    var a = 42;
    
    function b() { return a; }
    

    Tak. Prawdopodobnie używasz tego wiele razy dziennie.


      

    Nie ma powodu, aby sądzić, że zamknięcie to złożony hak projektowy do rozwiązania określonych problemów. Nie, zamknięcia używają tylko zmiennej pochodzącej z wyższego zakresu z perspektywy miejsca, w którym funkcja została zadeklarowana (nie uruchomiona) .

         

    Teraz to, co pozwala zrobić, może być bardziej spektakularne, zobacz inne odpowiedzi.

        
    102
    2015-02-21 23: 48: 56Z
    1. Ta odpowiedź nie wydaje się pomóc osobom, które nie zdają sobie sprawy. Szorstkim odpowiednikiem tradycyjnego języka programowania może być utworzenie b () jako metody obiektu, która także ma stałą prywatną lub właściwość a. Moim zdaniem niespodzianką jest to, że obiekt zasięgu JS skutecznie zapewnia a jako właściwość, a nie stałą. I zauważysz to ważne zachowanie, jeśli je zmodyfikujesz, jak w return a++;
      2015-05-15 01: 34: 07Z
    2. Dokładnie to, co powiedział Jon. Zanim w końcu zamknąłem drzwi, miałem trudności ze znalezieniem praktycznych przykładów. Tak, floribon stworzył zamknięcie, ale dla mnie niewykształconego nie nauczyłoby mnie absolutnie nic.
      2015-10-29 23: 15: 01Z
    3. To nie definiuje, czym jest zamknięcie - to tylko przykład, który go używa. I nie dotyczy niuansów tego, co dzieje się po zakończeniu zakresu; janie sądzę, aby ktokolwiek miał pytanie dotyczące zakresu leksykalnego, gdy wszystkie zakresy są nadal w pobliżu, a zwłaszcza w przypadku zmiennej globalnej.
      2016-08-09 15: 51: 36Z

    Przykład pierwszego punktu przez dlaliberte:

      

    Zamknięcie nie jest tworzone tylko po zwróceniu funkcji wewnętrznej. W rzeczywistości funkcja obejmująca nie musi w ogóle wracać. Zamiast tego możesz przypisać swoją wewnętrzną funkcję do zmiennej w zewnętrznym zakresie lub przekazać ją jako argument do innej funkcji, w której mogłaby zostać użyta natychmiast. Dlatego zamknięcie funkcji obejmującej prawdopodobnie już istnieje w momencie wywołania funkcji obejmującej, ponieważ każda funkcja wewnętrzna ma do niej dostęp natychmiast po wywołaniu.

     
    var i;
    function foo(x) {
        var tmp = 3;
        i = function (y) {
            console.log(x + y + (++tmp));
        }
    }
    foo(2);
    i(3);
    
        
    91
    2016-01-16 02: 39: 35Z
    1. FYI: uruchomienie powyższych pokazów = > 9
      2010-05-19 20: 24: 03Z
    2. Małe wyjaśnienie na temat możliwej dwuznaczności. Kiedy powiedziałem: „W rzeczywistości funkcja obejmująca nie musi w ogóle wracać”. Nie miałem na myśli „nie zwracaj wartości”, ale „nadal aktywny”. Przykład nie pokazuje tego aspektu, chociaż pokazuje inny sposób, w jaki funkcja wewnętrzna może zostać przekazana do zewnętrznego zakresu. Głównym punktem, który starałem się zrobić, jest czas stworzenia zamknięcia (dla funkcji zamykającej), ponieważ niektórzy ludzie wydają się myśleć, że dzieje się tak, gdy funkcja zamykająca się powraca. Potrzebny jest inny przykład, aby pokazać, że zamknięcie jest tworzone, gdy funkcja jest wywoływana .
      2011-07-21 14: 03: 12Z

    Zamknięcie jest wtedy, gdy funkcja wewnętrzna ma dostęp do zmiennych w swojej funkcji zewnętrznej. To prawdopodobnie najprostsze wyjaśnienie jednokreskowe, jakie możesz uzyskać dla zamknięć.

        
    86
    2012-12-24 11: 10: 56Z
    1. To tylko połowa wyjaśnienia. Ważną rzeczą, na którą należy zwrócić uwagę, jest to, że jeśli funkcja wewnętrzna jest nadal odwoływana po zakończeniu funkcji zewnętrznej, stare wartości funkcji zewnętrznej są nadal dostępne dla wewnętrznej.
      2008-09-21 22: 29: 13Z
    2. Właściwie to nie stare wartości funkcji zewnętrznej są dostępne dla funkcji wewnętrznej, ale stare zmienne , który może mieć nowe wartości, jeśli jakaś funkcja była w stanie je zmienić.
      2012-08-16 02: 39: 42Z

    Wiem, że istnieje już wiele rozwiązań, ale myślę, że ten mały i prosty skrypt może być przydatny do zademonstrowania koncepcji:

     
    // makeSequencer will return a "sequencer" function
    var makeSequencer = function() {
        var _count = 0; // not accessible outside this function
        var sequencer = function () {
            return _count++;
        }
        return sequencer;
    }
    
    var fnext = makeSequencer();
    var v0 = fnext();     // v0 = 0;
    var v1 = fnext();     // v1 = 1;
    var vz = fnext._count // vz = undefined
    
        
    84
    2016-05-09 11: 32: 47Z

    Śpisz i zapraszasz Dana. Mówisz Danowi, żeby przyniósł jeden kontroler XBox.

    Dan zaprasza Paula. Dan prosi Paula o przyniesienie jednego kontrolera. Ilu kontrolerów zostało przywiezionych na imprezę?

     
    function sleepOver(howManyControllersToBring) {
    
        var numberOfDansControllers = howManyControllersToBring;
    
        return function danInvitedPaul(numberOfPaulsControllers) {
            var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
            return totalControllers;
        }
    }
    
    var howManyControllersToBring = 1;
    
    var inviteDan = sleepOver(howManyControllersToBring);
    
    // The only reason Paul was invited is because Dan was invited. 
    // So we set Paul's invitation = Dan's invitation.
    
    var danInvitedPaul = inviteDan(howManyControllersToBring);
    
    alert("There were " + danInvitedPaul + " controllers brought to the party.");
    
        
    81
    2011-07-20 15: 16: 26Z

    Funkcje JavaScript mogą uzyskać dostęp do ich:

    1. Argumenty
    2. Miejscowi (czyli ich lokalne zmienne i funkcje lokalne)
    3. Środowisko, którenie obejmuje:
      • globalne, w tym DOM
      • wszystko w zewnętrznych funkcjach

    Jeśli funkcja uzyskuje dostęp do swojego środowiska, funkcja jest zamknięciem.

    Zauważ, że zewnętrzne funkcje nie są wymagane, chociaż oferują korzyści, których tutaj nie omawiam. Dzięki dostępowi do danych w swoim środowisku zamknięcie utrzymuje te dane przy życiu. W podjednostce funkcji zewnętrznych /wewnętrznych funkcja zewnętrzna może tworzyć dane lokalne i ostatecznie wyjść, a jednak jeśli jakakolwiek funkcja wewnętrzna przetrwa po zakończeniu funkcji zewnętrznej, wówczas funkcje wewnętrzne zachowują dane lokalne funkcji zewnętrznej żywy.

    Przykład zamknięcia wykorzystującego środowisko globalne:

    Wyobraź sobie, że zdarzenia przycisku Przepełnienie stosu i Głosowanie w dół są zaimplementowane jako zamknięcia, voteUp_click i voteDown_click, które mają dostęp do zmiennych zewnętrznych to VotedUp i isVotedDown, które są zdefiniowane globalnie. (Dla uproszczenia odnoszę się do przycisków StackOverflow Question Vote, a nie tablicy przycisków Answer Vote.)

    Gdy użytkownik kliknie przycisk VoteUp, funkcja voteUp_click sprawdza, czy isVotedDown == true, aby określić, czy głosować, czy tylko anulować głosowanie w dół. Funkcja voteUp_click jest zamknięciem, ponieważ uzyskuje dostęp do swojego środowiska.

     
    var isVotedUp = false;
    var isVotedDown = false;
    
    function voteUp_click() {
      if (isVotedUp)
        return;
      else if (isVotedDown)
        SetDownVote(false);
      else
        SetUpVote(true);
    }
    
    function voteDown_click() {
      if (isVotedDown)
        return;
      else if (isVotedUp)
        SetUpVote(false);
      else
        SetDownVote(true);
    }
    
    function SetUpVote(status) {
      isVotedUp = status;
      // Do some CSS stuff to Vote-Up button
    }
    
    function SetDownVote(status) {
      isVotedDown = status;
      // Do some CSS stuff to Vote-Down button
    }
    

    Wszystkie te cztery funkcje są zamknięciami, ponieważ wszystkie mają dostęp do swojego środowiska.

        
    77
    2016-06-08 22: 16: 22Z

    Autor Zamknięć wyjaśnił całkiem dobrze zamknięcie, wyjaśniając powód dlaczego ich potrzebujemy, a także wyjaśnienie środowiska LexicalEn, które jest niezbędne do zrozumienia zamknięcia.
    Oto podsumowanie:

    Co się stanie, jeśli zmienna jest dostępna, ale nie jest lokalna? Jak tutaj:

     Wpisz opis obrazu tutaj>> </a> </p>

<p> W tym przypadku interpreter znajduje zmienną w
zewnętrzny <a href=LexicalEnvironment obiekt.

    Proces składa się z dwóch kroków:

    1. Po pierwsze, gdy funkcja f jest tworzona, nie jest tworzona w pustym przestrzeń. Istnieje bieżący obiekt LexicalEnvironment. W tej sprawie powyżej, jest to okno (a jest niezdefiniowane w czasie działania tworzenie).

     Wpisz opis obrazu tutaj>> </a> </p>

<p> Gdy funkcja jest tworzona, pobiera ukrytą właściwość o nazwie [[Zakres]], która odwołuje się do bieżącego środowiska LexicalEnvironment. </p>

<p> <a href= Wpisz opis obrazu tutaj>> </a> </p>

<p> Jeśli zmienna jest odczytywana, ale nie można jej znaleźć w żadnym miejscu, generowany jest błąd. </p>

<p> <strong> Zagnieżdżone funkcje </strong> </p>

<p> Funkcje mogą być zagnieżdżone jeden w drugim, tworząc łańcuch środowisk LexicalEn, które można również nazwać łańcuchem zasięgu. </p>

<p> <a href= Wpisz opis obrazu tutaj>> </a> </p>

<p> Więc funkcja g ma dostęp do g, a i f. </p>

<p><strong>Closures</strong> </p>

<p> Zagnieżdżona funkcja może nadal działać po zakończeniu funkcji zewnętrznej: </p>

<p> <a href= Wpisz opis obrazu tutaj>> </a> </p>

<p> Zaznaczanie środowisk LexicalEn: </p>

<p> <a href= Wpisz opis obrazu tutaj>> </a> </p>

<p> Jak widzimy, <code>this.say</code> jest własnością obiektu użytkownika, więc nadal działa po ukończeniu przez użytkownika. </p>

<p> A jeśli pamiętasz, gdy tworzony jest <code>this.say</code>, to (jak każda funkcja) pobiera wewnętrzne odniesienie <code>this.say.[[Scope]]</code> do bieżącego środowiska LexicalEnvironment. Tak więc środowisko LexicalEnvironment bieżącego wykonania użytkownika pozostaje w pamięci. Wszystkie zmienne użytkownika są również jego właściwościami, więc są one również starannie przechowywane, a nie jak zwykle śmieciowe. </p>

<p> <strong> Chodzi o to, aby zapewnić, że jeśli funkcja wewnętrzna chce uzyskać dostęp do zmiennej zewnętrznej w przyszłości, może to zrobić. </strong> </p>

<p> Podsumowując: </p>

<ol>
<li> Funkcja wewnętrzna utrzymuje odniesienie do zewnętrznej
LexicalEnvironment. </li>
<li> Funkcja wewnętrzna może uzyskiwać dostęp do zmiennych z niej
w dowolnym momencie, nawet jeśli funkcja zewnętrzna jest zakończona. </li>
<li> Przeglądarka zachowuje środowisko LexicalEnvironment i wszystkie jego właściwości (zmienne) wpamięć, aż pojawi się wewnętrzna funkcja, która ją odwołuje. </li>
</ol>
<p> Nazywa się to zamknięciem. </p>
    </div>
<div class = 75

    2018-05-14 20: 51: 20Z

    Jako ojciec 6-latka, obecnie uczącego małe dzieci (i względnego nowicjusza do kodowania bez formalnego wykształcenia, więc wymagane będą poprawki), myślę, że lekcja najlepiej będzie się odbywać poprzez grę praktyczną. Jeśli sześciolatek jest gotowy zrozumieć, czym jest zamknięcie, to są na tyle dojrzałe, że same muszą sobie poradzić. Proponuję wkleić kod do jsfiddle.net, trochę wyjaśniając i pozostawiając ich samych, by wymyślili unikalną piosenkę. Poniższy tekst wyjaśniający jest prawdopodobnie bardziej odpowiedni dla 10-latka.

     
    function sing(person) {
    
        var firstPart = "There was " + person + " who swallowed ";
    
        var fly = function() {
            var creature = "a fly";
            var result = "Perhaps she'll die";
            alert(firstPart + creature + "\n" + result);
        };
    
        var spider = function() {
            var creature = "a spider";
            var result = "that wiggled and jiggled and tickled inside her";
            alert(firstPart + creature + "\n" + result);
        };
    
        var bird = function() {
            var creature = "a bird";
            var result = "How absurd!";
            alert(firstPart + creature + "\n" + result);
        };
    
        var cat = function() {
            var creature = "a cat";
            var result = "Imagine That!";
            alert(firstPart + creature + "\n" + result);
        };
    
        fly();
        spider();
        bird();
        cat();
    }
    
    var person="an old lady";
    
    sing(person);
    

    DATA: Dane to zbiór faktów. Mogą to być liczby, słowa, pomiary, obserwacje, a nawet opisy rzeczy. Nie możesz go dotknąć, powąchać go lub spróbować. Możesz to zapisać, mówić i słyszeć. Możesz go użyć, aby utworzyć zapach i smak za pomocą komputera. Może to być przydatne przez komputer za pomocą kodu.

    KOD: Wszystkie powyższe zapisy nazywają się kod . Jest napisany w JavaScript.

    JAVASCRIPT: JavaScript to język. Podobnie jak angielski lub francuski lub chiński są językami. Istnieje wiele języków zrozumiałych dla komputerów i innych procesorów elektronicznych. Aby JavaScript był zrozumiały dla komputera, potrzebuje tłumacza. Wyobraź sobie, że nauczyciel, który tylko mówi po rosyjsku, przychodzi uczyć klasę w szkole. Gdy nauczyciel powie „все садятся”, klasa nie zrozumie. Ale na szczęście masz w klasie rosyjskiego ucznia, który mówi wszystkim, że oznacza to, że „wszyscy usiądą” - więc wszyscy tak robią. Klasa jest jak komputer, a rosyjski uczeń jest tłumaczem. W JavaScript najbardziej popularnym tłumaczem jest przeglądarka.

    PRZEGLĄDARKA: Gdy łączysz się z Internetem na komputerze, tablecie lub telefonie, aby odwiedzić stronę internetową, używasz przeglądarki. Przykładami mogą być Internet Explorer, Chrome, Firefox i Safari. Przeglądarka może zrozumieć JavaScript i powiedzieć komputerowi, co ma zrobić. Instrukcje JavaScript nazywane są funkcjami.

    FUNKCJA: Funkcja w JavaScript jest jak fabryka. Może to być mała fabryka z jedną maszyną w środku. Może też zawierać wiele innych małych fabryk, z których każda ma wiele maszyn wykonujących różne zadania. W prawdziwej fabryce ubrań możesz mieć ryzę tkanin i szpulki nici, a także wychodzą koszulki i dżinsy. Nasza fabryka JavaScript przetwarza tylko dane, nie może szyć, wywiercić otworu ani stopić metalu. W naszym języku JavaScript dane fabryczne są wprowadzane, a dane wychodzą.

    Wszystkie te dane brzmią trochę nudno, ale jest naprawdę bardzo fajnie; możemy mieć funkcję, która mówi robotowi, co zrobić na obiad. Powiedzmy, że zapraszam ciebie i twojego przyjaciela do mojego domu. Lubisz udka z kurczaka, lubię kiełbaski, twój przyjaciel zawsze chce tego, co chcesz, a mój przyjaciel nie je mięsa.

    Nie mam czasu na zakupy, więc funkcja musi wiedzieć, co mamy w lodówce, aby podejmować decyzje. Każdy składnik ma inny czas gotowania i chcemy, aby wszystko było podawane na gorąco przez robota w tym samym czasie. Musimy udostępnić funkcję z danymi o tym, co lubimy, funkcja może „rozmawiać” z lodówką, a funkcja może sterować robotem.

    Funkcja zwykle ma nazwę, nawiasy i nawiasy klamrowe. W ten sposób:

     
    function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }
    

    Zauważ, że /*...*/ i // zatrzymują odczyt kodu przez przeglądarkę.

    NAZWA: Możesz wywołać funkcję niezależnie od tego, jakie słowo chcesz. Przykład „cookMeal” jest typowy w łączeniu dwóch słów razem i nadaniu drugiemu dużej litery na początku - ale nie jest to konieczne. Nie może mieć w nim spacji i nie może być liczbą samą w sobie.

    PARENTHESES: „Nawiasy” lub () to skrzynka na listy w drzwiach fabryki funkcji JavaScript lub skrzynka pocztowa na ulicy do wysyłania pakietów informacji do fabryki. Czasami skrzynka pocztowa może być oznaczona na przykład cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime), w którym to przypadku wiesz, jakie dane musisz jej podać.

    BRACES: „Szelki”, które wyglądają tak jak {}, to przyciemnione szyby naszej fabryki. Z wnętrza fabryki widać, ale z zewnątrz nie widać.

    PRZYKŁAD DŁUGIEGO KODU POWYŻEJ

    Nasz kod zaczyna się od słowa funkcja , więc wiemy, że jest to jeden! Następnie nazwa funkcji sing - to mój własny opis tego, o co chodzi w funkcji. Następnie nawiasy () . Nawiasy są zawsze dostępne dla funkcji. Czasami są puste, a czasamiMasz coś w środku. Ten ma słowo: (person). Po tym pojawia się klamra taka jak ta {. Oznacza to początek funkcji sing () . Ma partnera, który oznacza koniec sing () w ten sposób }

     
    function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }
    

    Ta funkcja może mieć coś wspólnego ze śpiewaniem i może potrzebować danych o osobie. Ma wewnątrz instrukcje, aby zrobić coś z tymi danymi.

    Teraz, po funkcji sing () , pod koniec kodu znajduje się linia

     
    var person="an old lady";
    

    ZMIENNA: Litery var oznaczają „zmienną”. Zmienna jest jak koperta. Na zewnątrz ta koperta jest oznaczona „osoba”. Wewnątrz zawiera kartkę papieru z informacjami, których potrzebuje nasza funkcja, niektóre litery i spacje połączone ze sobą jak kawałek sznurka (nazywany sznurkiem), które tworzą wyrażenie „starsza pani”. Nasza koperta może zawierać inne rodzaje rzeczy, takie jak liczby (zwane liczbami całkowitymi), instrukcje (zwane funkcjami), listy (zwane tablice ). Ponieważ ta zmienna jest zapisywana poza wszystkimi nawiasami klamrowymi {}, a ponieważ możesz zobaczyć przez przyciemnione okna, gdy jesteś wewnątrz nawiasów klamrowych, ta zmienna może być widoczna z dowolnego miejsca w kodzie. Nazywamy to „zmienną globalną”.

    GLOBALNA ZMIENNA: osoba to zmienna globalna, co oznacza, że ​​jeśli zmienisz jej wartość z „starej” na „młody człowiek”, osoba zachowa bycie młodym człowiekiem, dopóki nie zdecydujesz się go zmienić i że każda inna funkcja w kodzie może zobaczyć, że jest to młody człowiek. Naciśnij przycisk F12 lub spójrz na ustawienia opcji, aby otworzyć konsolę programisty przeglądarki i wpisz „osoba”, aby zobaczyć, jaka jest ta wartość. Wpisz person="a young man", aby go zmienić, a następnie wpisz ponownie „person”, aby zobaczyć, że się zmienił.

    Po tym mamy linię

     
    sing(person);
    

    Ta linia wywołuje funkcję, jakby wywołała psa

      

    „Chodź śpiewaj , chodź i pobierz osobę !”

    Gdy przeglądarka załaduje kod JavaScript osiągnie tę linię, uruchomi funkcję. Umieszczam linię na końcu, aby upewnić się, że przeglądarka ma wszystkie informacje potrzebne do jej uruchomienia.

    Funkcje definiują akcje - główną funkcją jest śpiewanie. Zawiera zmienną o nazwie firstPart , która dotyczy śpiewu o osobie, który odnosi się do każdego z wersetów utworu: „Było” + osoba + „kto połknął”. Jeśli wpiszesz firstPart w konsoli, nie otrzymasz odpowiedzi, ponieważ zmienna jest zablokowana w funkcji - przeglądarka nie widzi wewnątrz przyciemnianych okien nawiasów klamrowych.

    ZAMKNIĘCIA: Zamknięcia to mniejsze funkcje znajdujące się w dużej funkcji sing () . Małe fabryki w dużej fabryce. Każdy z nich ma własne szelki, co oznacza, że ​​zmienne w nich nie są widoczne z zewnątrz. Dlatego nazwy zmiennych ( stworzenie i wynik ) można powtórzyć w zamknięciach, ale z różnymi wartościami. Jeśli wpiszesz te nazwy zmiennych w oknie konsoli, nie otrzymasz ich wartości, ponieważ jest ona ukryta przez dwie warstwy przyciemnionych okien.

    Zamknięcia wiedzą, czym jest zmienna funkcji sing () o nazwie firstPart , ponieważ widzą z przyciemnionych okien.

    Po zamknięciu przychodzą linie

     
    fly();
    spider();
    bird();
    cat();
    

    Funkcja sing () wywoła każdą z tych funkcji w podanej kolejności. Następnie zostanie wykonana praca funkcji sing ().

        
    59
    2016-06-08 22: 11: 57Z

    Dobra, rozmawiając z 6-letnim dzieckiem, prawdopodobnie użyję następujących skojarzeń.

      

    Wyobraź sobie - bawisz się ze swoimi młodszymi braćmi i siostrami w całym domu, a ty poruszasz się swoimi zabawkami i przynosisz niektóre z nich do pokoju swojego starszego brata. Po chwili twój brat wrócił ze szkoły i poszedł do swojego pokoju, a on zamknął się w nim, więc teraz nie można było uzyskać bezpośredniego dostępu do zabawek, które tam pozostały. Ale możesz zapukać do drzwi i poprosić brata o te zabawki. To się nazywa zamknięcie zabawki ; twój brat to dla ciebie wymyślił, a teraz znajduje się w zewnętrznym zasięgu .

    Porównaj z sytuacją, gdy drzwi były zablokowane przez przeciąg i nikt w środku (ogólne wykonanie funkcji), a następnie nastąpił jakiś lokalny pożar i spalić pokój (śmieciarka: D), a następnie zbudowano nowy pokój i teraz możesz zostawić tam inne zabawki (nowa instancja funkcji), ale nigdy nie uzyskaj tych samych zabawek, które pozostały w pierwszej instancji pokoju.

    Dla zaawansowanego dziecka umieściłbym coś takiegoing. To nie jest idealne, ale sprawia, że ​​czujesz, co to jest:

     
    function playingInBrothersRoom (withToys) {
      // We closure toys which we played in the brother's room. When he come back and lock the door
      // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
      var closureToys = withToys || [],
          returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.
    
      var brotherGivesToyBack = function (toy) {
        // New request. There is not yet closureToys on brother's hand yet. Give him a time.
        returnToy = null;
        if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.
    
          for ( countIt = closureToys.length; countIt; countIt--) {
            if (closureToys[countIt - 1] == toy) {
              returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
              break;
            }
          }
          returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
        }
        else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
          returnToy = 'Behold! ' + closureToys.join(', ') + '.';
          closureToys = [];
        }
        else {
          returnToy = 'Hey, lil shrimp, I gave you everything!';
        }
        console.log(returnToy);
      }
      return brotherGivesToyBack;
    }
    // You are playing in the house, including the brother's room.
    var toys = ['teddybear', 'car', 'jumpingrope'],
        askBrotherForClosuredToy = playingInBrothersRoom(toys);
    
    // The door is locked, and the brother came from the school. You could not cheat and take it out directly.
    console.log(askBrotherForClosuredToy.closureToys); // Undefined
    
    // But you could ask your brother politely, to give it back.
    askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
    askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
    askBrotherForClosuredToy(); // The brother gives you all the rest
    askBrotherForClosuredToy(); // Nothing left in there
    

    Jak widać, zabawki pozostawione w pokoju są nadal dostępne dla brata i nie ważne, czy pokój jest zamknięty. Oto jsbin do zabawy.

        
    55
    2014-10-25 22: 52: 13Z

    Odpowiedź dla sześciolatka (zakładając, że wie, czym jest funkcja i czym jest zmienna oraz jakie dane są):

    Funkcje mogą zwracać dane. Jeden rodzaj danych, które możesz zwrócić z funkcji, jest inną funkcją. Gdy ta nowa funkcja zostanie zwrócona, wszystkie zmienne i argumenty użyte w funkcji, która ją stworzyła, nie znikną. Zamiast tego ta funkcja nadrzędna „zamyka się”. Innymi słowy, nic nie może zajrzeć do środka i zobaczyć zmiennych, których użył, z wyjątkiem funkcji, którą zwrócił. Ta nowa funkcja ma specjalną zdolność do spoglądania wstecz do funkcji, która ją utworzyła i zobaczenia w niej danych.

     
    function the_closure() {
      var x = 4;
      return function () {
        return x; // Here, we look back inside the_closure for the value of x
      }
    }
    
    var myFn = the_closure();
    myFn(); //=> 4
    

    Innym bardzo prostym sposobem wyjaśnienia tego jest zakres:

    Za każdym razem, gdy tworzysz mniejszy zakres wewnątrz większego zakresu, mniejszy zakres zawsze będzie w stanie zobaczyć, co znajduje się w większym zakresie.

        
    50
    2014-10-25 23: 02: 19Z
    1. Wygląda na to, że zamknięcie jest równoważne klasom i klasom wewnętrznym w O.O.
      2017-02-15 06: 29: 45Z

    Funkcja w JavaScript nie jest tylko odwołaniem do zestawu instrukcji (jak w języku C), ale zawiera również ukrytą strukturę danych, która składa się z odniesień do wszystkich nielokalnych zmiennych, których używa (przechwycone zmienne). Takie dwuczęściowe funkcje nazywane są zamknięciami. Każda funkcja w JavaScript może być uważana za zamknięcie.

    Zamknięcia są funkcjami ze stanem. Jest to nieco podobne do „to” w tym sensie, że „to” zapewnia również stan dla funkcji, ale funkcja i „to” są oddzielnymi obiektami („to” jest po prostu wymyślnym parametrem i jedynym sposobem na związanie go na stałe z funkcja polega na utworzeniu zamknięcia). Podczas gdy „to” i funkcja zawsze działają oddzielnie, funkcji nie można oddzielić od jej zamknięcia, a język nie zapewnia dostępu do przechwyconych zmiennych.

    Ponieważ wszystkie te zewnętrzne zmienne, do których odwołuje się funkcja zagnieżdżona leksykalnie, są w rzeczywistości zmiennymi lokalnymi w łańcuchu funkcji leksykalnych (zmienne globalne można przyjąć za zmienne lokalne niektórych funkcji root), a każde pojedyncze wykonanie funkcji tworzy nowe instancje zmiennych lokalnych wynika z tego, że każde wykonanie funkcji zwracającej (lub w inny sposób przekazującej, takiej jak rejestracja jako wywołanie zwrotne) funkcji zagnieżdżonej tworzy nowe zamknięcie (z własnym potencjalnie unikalnym zestawem odnośnych zmiennych nielokalnych, które reprezentują jego kontekst wykonania).

    Należy również rozumieć, że lokalne zmienne w JavaScript są tworzone nie na ramce stosu, ale na stercie i niszczone tylko wtedy, gdy nikt ich nie odwołuje. Gdy funkcja zwraca, odwołania do jej zmiennych lokalnych są zmniejszane, ale nadal mogą być niezerowe, jeśli podczas bieżącego wykonywania stały się częścią zamknięcia i nadal są przywoływane przez funkcje leksykalnie zagnieżdżone (co może się zdarzyć tylko wtedy, gdy odniesienia do te zagnieżdżone funkcje zostały zwrócone lub w inny sposób przeniesione do zewnętrznego kodu).

    Przykład:

     
    function foo (initValue) {
       //This variable is not destroyed when the foo function exits.
       //It is 'captured' by the two nested functions returned below.
       var value = initValue;
    
       //Note that the two returned functions are created right now.
       //If the foo function is called again, it will return
       //new functions referencing a different 'value' variable.
       return {
           getValue: function () { return value; },
           setValue: function (newValue) { value = newValue; }
       }
    }
    
    function bar () {
        //foo sets its local variable 'value' to 5 and returns an object with
        //two functions still referencing that local variable
        var obj = foo(5);
    
        //Extracting functions just to show that no 'this' is involved here
        var getValue = obj.getValue;
        var setValue = obj.setValue;
    
        alert(getValue()); //Displays 5
        setValue(10);
        alert(getValue()); //Displays 10
    
        //At this point getValue and setValue functions are destroyed
        //(in reality they are destroyed at the next iteration of the garbage collector).
        //The local variable 'value' in the foo is no longer referenced by
        //anything and is destroyed too.
    }
    
    bar();
    
        
    49
    2016-05-05 16: 04: 06Z

    Być może trochę poza wszystkimi, ale najbardziej przedwczesnymi sześciolatkami, ale kilka przykładów, które pomogły mi w koncepcji kliknięcia w JavaScript.

    Zamknięcie jest funkcją, która ma dostęp do zakresu innej funkcji (jej zmiennych i funkcji). Najprostszym sposobem utworzenia zamknięcia jest funkcja w obrębie funkcji; Powodem jest to, że w JavaScript funkcja zawsze ma dostęp do zakresu zawierającego ją funkcji.

     
    function outerFunction() {
        var outerVar = "monkey";
        
        function innerFunction() {
            alert(outerVar);
        }
        
        innerFunction();
    }
    
    outerFunction();

    ALERT: małpa

    W powyższym przykładzie wywoływana jest funkcja outerFunction, która z kolei wywołuje innerFunction. Zauważ, że zewnętrznaVar jest dostępna dla innerFunction, czego dowodem jest jej prawidłowe alarmowanie o wartości outerVar.

    Rozważ teraz:

     
    function outerFunction() {
        var outerVar = "monkey";
        
        function innerFunction() {
            return outerVar;
        }
        
        return innerFunction;
    }
    
    var referenceToInnerFunction = outerFunction();
    alert(referenceToInnerFunction());

    ALERT: małpa

    referenceToInnerFunction ma wartość outerFunction (), która po prostu zwraca odwołanie do innerFunction. Gdy wywoływana jest funkcja referenceToInnerFunction, zwraca wartość outerVar. Ponownie, jak powyżej, pokazuje to, że innerFunction ma dostęp do outerVar, zmiennej outerFunction. Co więcej, warto zauważyć, że zachowuje ten dostęp nawet po zakończeniu działania outerFunction.

    I tutaj dzieje się naprawdę interesująco. Gdybyśmy pozbyli się funkcji outerFunction, powiedzmy ustaw ją na null, można by pomyśleć, że referenceToInnerFunction utraci dostęp do wartości outerVar. Ale tak nie jest.

     
    function outerFunction() {
        var outerVar = "monkey";
        
        function innerFunction() {
            return outerVar;
        }
        
        return innerFunction;
    }
    
    var referenceToInnerFunction = outerFunction();
    alert(referenceToInnerFunction());
    
    outerFunction = null;
    alert(referenceToInnerFunction());

    ALERT: małpa ALERT: małpa

    Ale jak to jest? W jaki sposób referenceToInnerFunction może nadal znać wartość outerVar teraz, gdy outerFunction zostało ustawione na null?

    Powodem, dla którego referenceToInnerFunction może nadal uzyskiwać dostęp do wartości outerVar, jest to, że gdy zamknięcie zostało utworzone po raz pierwszy przez umieszczenie innerFunction wewnątrz outerFunction, innerFunction dodało odwołanie do zakresu outerFunction (jego zmiennych i funkcji) do łańcucha zasięgu. Oznacza to, że innerFunction ma wskaźnik lub odwołanie do wszystkich zmiennych outerFunction, w tym zmiennych zewnętrznych. Tak więc nawet po zakończeniu wykonywania funkcji outerFunction lub nawet jeśli została ona usunięta lub ustawiona na wartość null, zmienne w jej zakresie, takie jak outerVar, trzymają się w pamięci z powodu wyjątkowego odniesienia do nich w części innerFunction, która została zwrócona referenceToInnerFunction. Aby naprawdę uwolnić pamięć zewnętrzną i pozostałe zmienne outerFunction z pamięci, musiałbyś pozbyć się tych wyjątkowych odniesień do nich, powiedzmy, ustawiając wartość referenceToInnerFunction na null.

    //////////

    Dwie inne rzeczy o zamknięciu do odnotowania. Po pierwsze, zamknięcie zawsze będzie miało dostęp do ostatnich wartości jego funkcji zawierającej.

     
    function outerFunction() {
        var outerVar = "monkey";
        
        function innerFunction() {
            alert(outerVar);
        }
        
        outerVar = "gorilla";
    
        innerFunction();
    }
    
    outerFunction();

    ALERT: goryl

    Po drugie, gdy tworzone jest zamknięcie, zachowuje ono odniesienie do wszystkich zmiennych i funkcji zawartych w funkcji; nie można wybrać i wybrać. I tak, zamknięcia powinny być używane oszczędnie lub przynajmniej ostrożnie, ponieważ mogą być intensywne dla pamięci; wiele zmiennych może być przechowywanych w pamięci długo po zakończeniu wykonywania funkcji zawierającej.

        
    48
    2015-04-29 15: 37: 06Z

    Po prostu wskazałbym ich na stronę Mozilla Closures . To najlepsze, najbardziej zwięzłe i proste wyjaśnienie z podstaw zamknięcia i praktycznego użycia, które znalazłem. Jest wysoce zalecane każdemu, kto uczy się JavaScript.

    I tak, poleciłbym nawet 6-latkowi - jeśli sześciolatek uczy się o zamknięciach, to logiczne jest, że są gotowi zrozumieć zwięzłe i proste wyjaśnienie podane w artykule.

        
    45
    2014-10-25 22: 54: 15Z
    1. Zgadzam się: wspomniana strona Mozilli jest szczególnie prosta i zwięzła. O dziwo, twój post nie został tak doceniony, jak inni.
      2018-04-28 08: 21: 59Z
źródło umieszczone tutaj