Jak działają zamknięcia JavaScript?

Jak wyjaśniłbyś zamknięcia JavaScript komuś, kto zna pojęcia, z których się składają (na przykład funkcje, zmienne i tym podobne), ale nie rozumie samych zamknięć?

Widziałem Przykład schematu podany na Wikipedii, ale niestety nie pomógł.

Author: Zaheer Ahmed, 2008-09-21

30 answers

Zamknięcia JavaScript dla początkujących

Submitted by Morris on WT., 2006-02-21 10:19. Społeczność-redagowane 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 funkcyjnych.

Zamknięcia są nie są trudne do zrozumienia, gdy podstawowe pojęcie jest grokked. Są jednak niemożliwe do zrozumienia przez czytanie jakichkolwiek teoretycznych lub akademickie wyjaśnienia!

[ ... ], Który jest przeznaczony dla programistów z pewnym doświadczeniem programistycznym w głównym języku, którzy mogą przeczytać następującą funkcję JavaScript: [ ... ]]}

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

Dwa krótkie streszczenia

  • 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ę jedynie niewidoczne dla świat zewnętrzny. Foo może więc sprytnie zwracać funkcje bar i baz, i mogą nadal czytać, pisać i komunikować się ze sobą za pomocą tej zamkniętej rodziny zmiennych ("closure"), z którą nikt inny nie może się mieszać, nawet ktoś, kto w przyszłości ponownie wywoła foo.

  • Zamknięcie jest jednym ze sposobów obsługi funkcji klasy pierwszej; jest to wyrażenie, które może odwoływać się do zmiennych w swoim zakresie( kiedy zostało po raz pierwszy zadeklarowane), być przypisane do zmienna, być przekazywana jako argument do funkcji lub być zwracana jako wynik funkcji.

Przykład zamknięcia

Następujący 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, jak odwołanie do funkcji jest zwracane do zmiennej (say2) w powyższym kodzie. Jeśli nie, to musisz na to spojrzeć, zanim nauczysz się zamykania. Programista używający C pomyślałby o funkcja jako zwracanie wskaźnika do funkcji i że zmienne say i say2 były wskaźnikiem do funkcji.

Istnieje krytyczna różnica między wskaźnikiem C do funkcji a odniesieniem JavaScript do funkcji. W JavaScript można myśleć o zmiennej referencyjnej funkcji jako o wskaźniku do funkcji , jak również jako ukrytym wskaźniku do zamknięcia.

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

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

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

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

Patrząc na wyjście 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ęcie.

Genialne jest to, że w JavaScript odwołanie do funkcji ma również tajne odniesienie do zamknięcia, w którym została utworzona - podobnie jak delegaty są wskaźnikiem metody plus tajne odniesienie do obiektu.

Więcej przykładów

Z jakiegoś powodu zamknięcia wydają się naprawdę trudne do zrozumienia, gdy się o nich czyta, ale kiedy widzisz kilka przykładów, staje się jasne, jak działają(Zajęło mi to chwilę). Polecam uważną pracę nad przykładami, aż do zrozum, jak działają. Jeśli zaczniesz używać zamknięć bez pełnego zrozumienia, jak one działają, wkrótce stworzysz 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 pojawieniu się 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 globalne funkcje mają wspólne odniesienie do to samo zamknięcie, ponieważ wszystkie są zadeklarowane w ramach jednego wywołania 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ólny dostęp do tego samego zamknięcia - zmiennych lokalnych setupSomeGlobals(), gdy te trzy funkcje zostały zdefiniowane.

Zauważ, że w powyższym przykładzie, jeśli ponownie wywołasz setupSomeGlobals(), to nowe zamknięcie (stack-frame!) jest tworzony. Stare gLogNumber, gIncreaseNumber, gSetNumber zmienne są nadpisywane nowymi funkcjami , które mają nowe zamknięcie. (W JavaScript, ilekroć deklarujesz funkcję wewnątrz innej funkcji, funkcje wewnętrzne są / są odtwarzane ponownie za każdym razem, gdy wywołana jest funkcja zewnętrzna.)

Przykład 5

Ten przykład pokazuje, że zamknięcie zawiera dowolne zmienne lokalne, które zostały zadeklarowane wewnątrz zewnętrznej funkcji przed jej zakończeniem. Zauważ, że zmienna alice jest faktycznie zadeklarowana po funkcji anonimowej. Funkcja anonimowa jest zadeklarowana jako pierwsza, a gdy ta funkcja jest wywołana może uzyskaj dostęp do zmiennej alice, ponieważ alice Znajduje się w tym samym zakresie (JavaScript robi podnoszenie zmiennej ). Również sayAlice()() po prostu bezpośrednio wywołuje referencję funkcji zwróconą z sayAlice() - jest dokładnie taka sama jak poprzednio, ale bez zmiennej tymczasowej.

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"

Tricky: zauważ również, że zmienna say jest również wewnątrz zamknięcia i może być dostępna przez dowolną inną funkcję, która może być zadeklarowana w sayAlice(), lub może być dostępna rekurencyjnie wewnątrz funkcji wewnętrznej.

Przykład 6

[77]} ten jest prawdziwą zdobyczą dla wielu ludzi, więc musisz to zrozumieć. Bądź bardzo ostrożny, jeśli definiujesz funkcję w pętli: lokalne zmienne Z Zamknięcia mogą nie działać tak, jak początkowo myślisz.

Aby zrozumieć ten przykład, musisz zrozumieć funkcję "variable hoisting" 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 odniesienie do funkcja anonimowa trzy razy do tablicy wyników. Jeśli nie jesteś zaznajomiony z funkcjami anonimowymi, pomyśl o tym w następujący sposób:]}

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

Zauważ, że po uruchomieniu przykładu, {[40] } jest rejestrowany trzy razy! Dzieje się tak, ponieważ podobnie jak poprzednie przykłady, istnieje tylko jedno zamknięcie dla zmiennych lokalnych dla buildList (które są result, i i item). Gdy funkcje anonimowe są wywoływane w linii fnlist[j](); wszystkie używają tego samego pojedynczego zamknięcia i używają bieżącej wartości dla i i item wewnątrz tego zamknięcia (gdzie i ma wartość 3 ponieważ pętla została zakoń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ę stanie, gdy używana jest deklaracja zmiennej item na poziomie bloków (za pomocą słowa kluczowego let) zamiast deklaracji zmiennej o zasięgu funkcji za pomocą słowa kluczowego var. Jeśli taka zmiana zostanie dokonana, to każdy funkcja anonimowa w tablicy result ma swoje własne zamknięcie; Po uruchomieniu przykładu wyjście wygląda następująco:

item0 undefined
item1 undefined
item2 undefined

Jeśli zmienna i jest również zdefiniowana za pomocą let zamiast var, to wyjście jest następujące:

item0 1
item1 2
item2 3

Przykład 7

W tym ostatnim przykładzie każde wywołanie funkcji głównej tworzy osobne 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, to najlepszą rzeczą do zrobienia jest pobawić się z przykłady. Czytanie wyjaśnień jest o wiele trudniejsze niż zrozumienie przykładów. Moje wyjaśnienia dotyczące zamknięć i stack-frame' ów itp. nie są poprawne pod względem technicznym - są to brutto uproszczenia mające na celu pomoc w zrozumieniu. Gdy podstawowa idea jest grokked, można odebrać szczegóły później.

Punkty końcowe:

  • gdy używasz function wewnątrz innej funkcji, używane jest zamknięcie.
  • gdy używasz eval() wewnątrz funkcji, używane jest zamknięcie. Tekst ty eval można odwoływać się do zmiennych lokalnych funkcji, a w obrębie eval Można nawet tworzyć nowe zmienne lokalne za pomocą eval('var foo = …')
  • kiedy używasz new Function(…) (konstruktorafunkcji ) wewnątrz funkcji, nie tworzy ona zamknięcia. (Nowa funkcja nie może odwoływać się do zmiennych lokalnych funkcji zewnętrznej.)
  • zamknięcie w JavaScript jest jak zachowanie kopii wszystkich zmiennych lokalnych, tak jak było po zakończeniu funkcji.
  • prawdopodobnie najlepiej jest pomyśleć, że closure jest zawsze tworzony tylko wpis funkcji, a zmienne lokalne są dodawane do tego zamknięcia.
  • nowy zestaw zmiennych lokalnych jest przechowywany za każdym razem, gdy wywołana jest funkcja z zamknięciem (biorąc pod uwagę, że funkcja zawiera deklarację funkcji wewnątrz niej, a odniesienie do tej funkcji wewnątrz jest zwracane lub zewnętrzne odniesienie jest przechowywane dla niej 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 z powodu ich "ukrytego" zamknięcia. Nie sądzę, że kod JavaScript może rzeczywiście dowiedzieć się, czy odwołanie do funkcji ma zamknięcie, czy nie.
  • jeśli próbujesz dokonać dynamicznych modyfikacji 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 nie pomyślisz o zamianie ciągu znaków kodu źródłowego w czasie wykonywania, ale...).
  • możliwe jest uzyskanie deklaracji funkcji w deklaracjach funkcji w funkcjach & mdash, i można uzyskać zamknięcia na więcej niż jednym poziomie.
  • myślę, że zwykle zamknięcie jest terminem dla obu funkcji wraz ze zmiennymi, które są przechwytywane. Zauważ, że nie używam tej definicji w tym artykule!
  • podejrzewam, że zamknięcia w JavaScript różnią się od tych normalnie występujących w językach funkcyjnych.

Linki

Dzięki

Jeśli masz Tylko nauczone zamknięcia (tu lub gdzie indziej!), następnie jestem zainteresowany wszelkimi opiniami od Ciebie na temat ewentualnych zmian, które możesz zasugerować, które mogłyby uczynić ten artykuł jaśniejszym. Wyślij e-mail do morrisjohns.com (morris_closure @). Proszę zauważyć, że nie jestem guru w JavaScript-ani w zamykaniu.


Original post by Morris w Internet Archive.

 6335
Author: flying sheep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-15 09:13:27

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);

To zawsze będzie logować 16, ponieważ {[4] } 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.

Że na zakończenie. Funkcja nie musi zwracać , aby została wywołana zamknięciem. prosty dostęp do zmiennych poza bezpośrednim zakresem leksykalnym tworzy 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 może nadal odnosić się do x i tmp, nawet jeśli nie znajduje się bezpośrednio wewnątrz zakresu.

Jednakże, ponieważ tmp wciąż wisi w środku zamknięcia bar, jest on również zwiększany. Będzie ona zwiększana za każdym razem, gdy wywołasz bar.

Najprostszym przykładem zamknięcia jest to:

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 otrzymuje również wszystkie zmienne zadeklarowane poza nim (w powyższym przykładzie, zarówno 'a', jak i 'b').

Można utworzyć więcej niż jedną funkcję zamykającą, zwracając ich listę lub ustawiając je na zmienne globalne. Wszystkie te będą odnosić się do tego samego x i to samo tmp, nie robią własnych kopii.

Tutaj Liczba x jest liczbą literalną. Podobnie jak w przypadku innych liter w JavaScript, gdy foo jest wywoływana, liczba x jest kopiowana do foo jako jej argument x.

Z drugiej strony, JavaScript zawsze używa odniesień do obiektów. Jeśli powiedzmy, wywołałeś {[6] } z obiektem, zamknięcie, które zwróci, będzie odwoływać się do tego oryginału sprzeciw!

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 się spodziewać, że x odnosi się po prostu do tego samego obiektu co zmienna age! Po kilku telefonach do bar, age.memb będzie 2! To odniesienie jest podstawą wycieków pamięci z obiektami HTML.

 3826
Author: Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-10 18:16:11

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

Jak powiedział stary Albert: "jeśli nie potrafisz tego wyjaśnić sześciolatkowi, to sam tego nie rozumiesz.". Cóż, próbowałem wyjaśnić zamknięcie JS 27-letniemu przyjacielowi i całkowicie się nie udało.

Czy ktoś może uznać, że mam 6 lat i dziwnie interesuję się tym tematem ?

Jestem prawie pewien, że byłem jedną z niewielu osób, które próbowały wziąć pierwsze pytanie dosłownie. Od następnie pytanie zmutowało kilka razy, więc moja odpowiedź może teraz wydawać się niewiarygodnie głupia i nie na miejscu. Mam nadzieję, że ogólna idea tej historii pozostanie zabawna dla niektórych.

Jestem wielkim fanem analogii i metafory przy wyjaśnianiu trudnych pojęć, więc pozwól mi spróbować swoich sił z historią.

Dawno, dawno temu:

Była sobie księżniczka...
function princess() {
Żyła w cudownym świecie pełnym przygód. Poznała swojego księcia z bajki, jeździła po świecie na jednorożec, walczyły ze smokami, spotykały gadające zwierzęta i wiele innych fantastycznych rzeczy.
    var adventures = [];

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

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

    /* ... */
Ale zawsze musiałaby wrócić do swojego nudnego świata obowiązków i dorosłych.
    return {
Często opowiadała im o swojej ostatniej niesamowitej przygodzie jako księżniczka.
        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}
Ale zobaczyliby tylko małą dziewczynkę...
var littleGirl = princess();

...opowiadania o magii i fantazji.

littleGirl.story();
I chociaż dorośli wiedzieli o prawdziwych księżniczkach, nigdy nie wierzcie w jednorożce i smoki, bo nigdy ich nie zobaczyli. Dorośli mówili, że istnieją tylko w wyobraźni dziewczynki. [6]}ale znamy prawdziwą prawdę; że dziewczynka z księżniczką w środku...

...jest naprawdę księżniczką z małą dziewczynką w środku.

 2273
Author: Jacob Swartwood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-01 11:40:09

Biorąc to pytanie na poważnie, powinniśmy dowiedzieć się, do czego zdolny jest typowy 6-latek poznawczo, choć trzeba przyznać, że ten, kto interesuje się JavaScript, nie jest taki typowy.

On rozwój dziecka: 5 do 7 lat pisze:

Twoje dziecko będzie mogło postępować zgodnie z dwuetapowymi wskazówkami. Na przykład, jeśli powiesz swojemu dziecku: "idź do kuchni i przynieś mi worek na śmieci", będą w stanie zapamiętać ten kierunek.

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

Kuchnia jest zamknięciem, które ma zmienną lokalną, zwaną trashBags. Istnieje funkcja wewnątrz kuchni o nazwie getTrashBag, która dostaje jeden worek na śmieci i zwraca go.

Możemy to kodować w JavaScript w następujący sposób:

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

Kolejne punkty, które wyjaśniają, dlaczego zamknięcia są interesujące:

  • Po każdym wywołaniu makeKitchen() tworzy się nowe zamknięcie z oddzielnym trashBags.
  • The trashBags zmienna jest lokalna do wnętrza każdej kuchni i nie jest dostępna na zewnątrz, ale wewnętrzna funkcja właściwości 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 być wywołana spoza zamknięcia. Zwrócenie obiektu za pomocą funkcji getTrashBag robi to tutaj.
 696
Author: dlaliberte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-10 17:50:14

Człowiek Ze Słomy

Muszę wiedzieć, ile razy przycisk został kliknięty i zrobić coś na co trzecie kliknięcie...

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 do zewnętrznego zakresu poprzez dodanie zmiennej, której jedynym celem jest śledzenie liczby. W niektórych sytuacjach byłoby to preferowane, ponieważ zewnętrzna aplikacja może potrzebować dostępu do tych informacji. Ale w tym przypadku zmieniamy tylko zachowanie co trzeciego kliknięcia, więc lepiej jest umieścić tę funkcję wewnątrz procedury 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.

W powyższym przykładzie używam zachowania zamknięcia JavaScript. to zachowanie pozwala dowolnej funkcji mieć dostęp do zakresu, w którym została utworzona, w nieskończoność. aby to praktycznie zastosować, natychmiast wywołuję funkcję, która zwraca inną funkcja, a ponieważ zwracana funkcja ma dostęp do wewnętrznej zmiennej count (z powodu zachowania zamknięcia opisanego powyżej), skutkuje to prywatnym zakresem użycia przez wynikową funkcję... Nie takie proste? Rozcieńczmy to...

Proste zamknięcie Jednowierszowe

//          _______________________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 zwracanej funkcji, ale nie są bezpośrednio dostępne dla zwracanego obiektu funkcji...

func();  // Alerts "val"
func.a;  // Undefined
Rozumiesz? Tak więc w naszym głównym przykładzie zmienna count jest zawarta w zamknięciu i zawsze dostępna dla obsługi zdarzenia, więc zachowuje swój stan od kliknięcia do kliknięcia.

Również ten Prywatny stan zmiennej jest w pełni dostępny, zarówno dla odczytów, jak i przypisywania do jej prywatnych zmiennych o zasięgu.

Proszę bardzo, teraz w pełni ujmujesz to zachowanie.

Pełny wpis na blogu (w tym rozważania jQuery)

 537
Author: jondavidjohn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-13 04:54:05

Zamknięcia są trudne do wyjaśnienia, ponieważ są używane do działania pewnych zachowań, które każdy intuicyjnie oczekuje, że i tak zadziałają. Ja uważam, że najlepszym sposobem na ich wyjaśnienie (i sposobem, w jaki nauczyłem się tego, 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 wiedział o zamknięciu? Wystarczy zastąpić wywołanie w ostatniej linii przez jego ciało metody (co jest w zasadzie tym, co wywołania funkcji zrobić) I ty get:

console.log(x + 3);
Gdzie jest definicja x? Nie zdefiniowaliśmy tego w obecnym zakresie. Jedynym rozwiązaniem jest pozwolić plus5 przenieś jego zakres (a raczej zakres jego rodzica) wokół. W ten sposób x jest dobrze zdefiniowany i jest związany z wartością 5.
 450
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-17 08:51:15

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

  • Zamknięcie jest tworzone nie tylko wtedy, gdy zwracasz wewnętrzną funkcję. w rzeczywistości funkcja zamykająca nie musi w ogóle zwracać , aby utworzyć jej zamknięcie. Możesz zamiast tego przypisać swoją wewnętrzną funkcję do zmiennej w zewnętrznym zakresie lub przekazać ją jako argument do innej funkcji, gdzie można ją natychmiast wywołać albo kiedykolwiek później. W związku z tym Zamknięcie funkcji zamykającej jest prawdopodobnie tworzone tak szybko, jak funkcja zamykająca jest wywoływana, ponieważ każda wewnętrzna funkcja ma dostęp do tego zamknięcia za każdym razem, gdy wewnętrzna funkcja jest wywoływana, przed lub po powrocie funkcji zamykającej.
  • Zamknięcie nie odwołuje się do kopiistarych wartości zmiennych w swoim zakresie. same zmienne są częścią zamknięcia, a więc wartość widziana przy dostępie do jednego z tych zmienna jest ostatnią wartością w momencie, w którym jest dostępna. Z tego powodu wewnętrzne funkcje utworzone wewnątrz pętli mogą być trudne, ponieważ każda z nich ma dostęp do tych samych zewnętrznych zmiennych, zamiast pobierać kopię zmiennych w czasie tworzenia lub wywoływania funkcji.
  • "zmienne" w zamknięciu zawierają dowolne nazwane funkcje zadeklarowane w ramach funkcji. Zawierają również argumenty funkcji. Zamknięcie ma również dostęp do swoich zmiennych zamknięcia, wszystkie aż do globalnego zasięgu.
  • zamknięcia używają pamięci, ale nie powodują wycieków pamięci , ponieważ JavaScript sam w sobie czyści własne okrągłe struktury, które nie są odwołane. Wycieki pamięci Internet Explorera związane z zamknięciami są tworzone, gdy nie odłącza wartości atrybutów DOM, które odwołują się do zamknięć, zachowując w ten sposób odniesienia do ewentualnie okrągłych struktur.
 344
Author: dlaliberte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-05 15:00:49

OK, 6-letni fan zamknięć. 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 samochodem, ale wewnątrz samolotu, nawet jeśli samolot opuszcza lotnisko, jest zamknięciem. To wszystko. Kiedy skończysz 27 lat, spójrz na bardziej szczegółowe wyjaśnienie lub na poniższy przykład.

Oto Jak mogę przekonwertować mój samolot historia w kodzie.

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");
 332
Author: Max Tkachenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-10 18:38:01

A zamknięcie jest podobne do obiektu. Jest inicjowana za każdym razem, gdy wywołujesz funkcję.

Zakres closure w JavaScript jest leksykalny, co oznacza, że wszystko, co jest zawarte w funkcji, do której należy closure , ma dostęp do każdej zmiennej, która się w niej znajduje.

Zmienna jest zawarta w Jeśli

  1. przypisać go z var foo=1; lub
  2. po prostu napisz var foo;

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

A zamknięcie przekracza czas działania funkcji, która ją wywołała. Jeśli inne funkcje wykażą się z closure / scope, w którym są zdefiniowane (na przykład jako wartości zwracane), będą nadal odwoływać się do tego, że zamknięcie .

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
 323
Author: Florian Bösch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-10 18:39:20

Jakiś czas temu napisałam post na blogu wyjaśniając Oto, co powiedziałem o zamknięciu w kategoriach Dlaczego Chcesz jeden.

Zamknięcia są sposobem na Niech funkcja mieć stałe, prywatne zmienne - czyli zmienne, które tylko jeden funkcja wie o tym, gdzie może śledź informacje z poprzednich czasów że został uruchomiony.

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

Full post:

Co to za zamknięcia?

 216
Author: Nathan Long,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-28 02:23:21

Zamknięcia są proste:

Poniższy prosty przykład obejmuje wszystkie główne punkty zamykania 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 będzie nadal używana przez funkcje add i multiply długo po zwróceniu make_calculator.

Jeśli znasz ramki stosu, te kalkulatory wydają się dziwne: jak mogą mieć dostęp do n po zwrocie make_calculator? Odpowiedzią jest wyobrażenie sobie, że JavaScript nie używa "ramek stosu", ale zamiast tego używa "ramek stosu", które mogą utrzymywać się po wywołaniu funkcji, które spowodowało ich powrót.

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

To jest prawie wszystko, co jest do zamknięcia.



* na przykład, obejmuje wszystkie punkty w artykule "zamknięcia dla manekinów" podanym w inna odpowiedź , Z wyjątkiem przykładu 6, który po prostu pokazuje, że zmienne mogą być używane przed ich zadeklarowaniem, co jest miłym faktem, ale zupełnie niezwiązanym z zamknięciami. Obejmuje ona również wszystkie punkty zaakceptowanej odpowiedzi , Z wyjątkiem punktów (1), w których funkcje kopiują swoje argumenty do zmiennych lokalnych (nazwanych argumenty funkcji), oraz (2) że kopiowanie liczb tworzy nową liczbę, ale kopiowanie odniesienia do obiektu daje kolejne odniesienie do tego samego obiektu. Są one również dobrze wiedzieć, ale ponownie całkowicie niezwiązane z zamknięciami. Jest również bardzo podobna do przykładu w ta odpowiedź , ale nieco krótsza i mniej abstrakcyjna. Nie obejmuje punktu tej odpowiedzi lub tego komentarza, czyli tego, że JavaScript utrudnia podłączenie bieżącej wartości zmienna loop into your inner function: krok "Podłączanie" można wykonać tylko za pomocą funkcji pomocniczej, która zamyka twoją wewnętrzną funkcję i jest wywoływana na każdej iteracji pętli. (Ściśle mówiąc, funkcja wewnętrzna uzyskuje dostęp do kopii zmiennej funkcji pomocniczej, zamiast mieć cokolwiek podłączonego.) Ponownie, bardzo przydatne podczas tworzenia zamknięć, ale nie część tego, co zamknięcie jest lub jak to działa. Istnieje dodatkowe zamieszanie ze względu na zamknięcia działające inaczej w językach funkcyjnych, takich jak ML, gdzie zmienne są związane z wartościami, a nie z przestrzenią magazynową, zapewniając stały strumień ludzi, którzy rozumieją zamknięcia w sposób (mianowicie sposób "podłączania"), który jest po prostu nieprawidłowy dla JavaScript, gdzie zmienne są zawsze związane z przestrzenią magazynową, a nigdy z wartościami.

** każda zewnętrzna funkcja, jeśli kilka jest zagnieżdżonych, lub nawet w kontekście globalnym, jak to wyraźnie wskazuje ta odpowiedź.

 199
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:46

Jak bym to wytłumaczył sześciolatkowi:

Wiesz, jak dorośli mogą mieć Dom i nazywają go domem? Kiedy mama ma dziecko, dziecko tak naprawdę niczego nie posiada, prawda? Ale jego rodzice są właścicielami domu, więc kiedy ktoś pyta dziecko " gdzie jest Twój dom?", on/ona może odpowiedzieć " ten dom!", i wskazać dom jego rodziców. "Zamknięcie" to zdolność dziecka do zawsze (nawet jeśli za granicą) być w stanie powiedzieć, że ma dom, mimo że to naprawdę rodzic, który jest właścicielem house.

 196
Author: Magne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-16 02:30:44

Czy możesz wyjaśnić zamknięcie 5-latka?*

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

/*
*    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 wewnętrzna funkcja nie zwraca

*A C # question

 187
Author: Chris S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:32

Zwykle uczę się lepiej przez dobre/złe porównania. Lubię widzieć działający kod, a następnie nie działający kod, który ktoś może napotkać. UĹ 'oĹźyĹ' em jsFiddle, ktĂłry dokonuje porăłwnania i stara siÄ ™ zrăłĺźnicowaä ‡ răłĺźnice do najprostszych wyjaĹ "nieĹ", jakie mogĹ 'em wymyĹ" lić.

Zamknięcie zrobione dobrze:

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 {[2] } jest wywoływany w każdej iteracji pętli. Zauważ, że nazwałem zmienną n, aby podkreślić, że jest to Nowa zmienna utworzona w nowym zakresie funkcji i nie jest tą samą zmienną co index, która jest związana z zewnętrznym zakresem.

  • Tworzy to nowy zakres i n jest z nim związany; oznacza to, że mamy 10 oddzielnych zakresów, po jednym dla każdej iteracji.

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

  • W każdym zakresie {[3] } jest związana z dowolną wartością, jaką miał, gdy createClosure(n) został wywołany, więc zagnieżdżona funkcja, która gets returned zawsze zwróci wartość n, jaką miał w momencie wywołania createClosure(n).

Zamknięcia zrobione ź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 wewnątrz funkcji createClosureArray() i funkcja zwraca teraz tylko wypełnioną tablicę, która na pierwszy rzut oka wydaje się bardziej intuicyjna.

  • Co może nie być oczywiste, ponieważ {[11] } jest wywoływany tylko wtedy, gdy tylko jeden zakres jest tworzony dla tej funkcji, a nie jeden dla każdego iteracja pętli.

  • W ramach tej funkcji zdefiniowana jest zmienna o nazwie index. Pętla uruchamia i dodaje funkcje do tablicy, które zwracają index. Zauważ, że {[4] } jest zdefiniowana w funkcji createClosureArray, która jest wywoływana tylko raz.

  • Ponieważ w funkcji createClosureArray() istniał tylko jeden zakres, {[4] } jest związany tylko z wartością w tym zakresie. Innymi słowy, za każdym razem, gdy pętla zmienia wartość index, zmienia ją dla wszystkiego, co odwołuje się do niego w tym zakresie.

  • Wszystkie funkcje dodane do tablicy zwracają tę samą zmienną index z zakresu nadrzędnego, gdzie została zdefiniowana zamiast 10 różnych funkcji z 10 różnych zakresów, jak w pierwszym przykładzie. 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ĘCIA ZROBIONE DOBRZE
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CLOSURES DONE WRONG
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

 164
Author: Chev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-27 17:56:11

Wikipedia o zamknięciach :

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

Technicznie, 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ęzyki, to, co zwykle rozumiemy przez zamknięcie w JavaScript jest funkcją pracującą ze zmiennymi nielokalnymi zdefiniowanymi w już wykonanej funkcji otaczającej .

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 wykorzystuje funkcję anonimową, która została wykonana raz. Ale nie musi tak być. Może być nazwany (np. mkdb) i wykonany później, generowanie funkcji bazy danych przy każdym jej wywołaniu. Każda wygenerowana funkcja będzie miała swój własny ukryty obiekt bazy danych. Innym przykładem użycia closures jest to, że nie zwracamy funkcji, ale obiekt zawierający wiele funkcji do różnych celów, przy czym każda z tych funkcji ma dostęp do tych samych danych.

 154
Author: mykhal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-18 16:48:34

Stworzył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
 128
Author: Nathan Whitehead,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-25 22:38:03

Dzieci zawsze będą pamiętać tajemnice, które podzieliły się z rodzicami, nawet po ich rodzice są zniknął. Tym właśnie są zamknięcia dla funkcji.

Tajemnicami dla funkcji JavaScript są zmienne prywatne

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

Za każdym razem, gdy ją wywołasz, lokalna zmienna "name" jest tworzona i otrzymuje nazwę "Mary". I za każdym razem, gdy funkcja opuszcza zmienną jest tracona i nazwa jest zapomniana.

Jak można się domyślić, ponieważ zmienne są tworzone ponownie za każdym razem, gdy funkcja jest wywoływana i nikt inny nie będzie ich znał, musi istnieć sekretne miejsce, w którym są przechowywane. Można ją nazwać Komnatą tajemnic lub stosem lub lokalnym zasięgiem, ale to nie ma znaczenia. Wiemy, że gdzieś tam są, ukryte w pamięci.

Ale w JavaScript jest taka szczególna rzecz, że funkcje, które są tworzone wewnątrz innych funkcji, mogą również znać lokalne zmienne swoich rodziców i zachować tak długo jak żyją.

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

Tak długo, jak jesteśmy w funkcji rodzica, może ona tworzyć jedną lub więcej funkcji potomnych, które dzielą tajne zmienne z tajnego miejsca.

[5]}ale smutne jest to, że jeśli dziecko jest również prywatną zmienną swojej funkcji rodzica, to również umrze, gdy rodzic się skończy, a sekrety umrą 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 " nie jest już running", pamięć o niej nie jest stracona, a jej dziecko zawsze będzie pamiętać jej imię i inne tajemnice, które dzieliły podczas ich wspólnego czasu. Więc, jeśli nazwiesz dziecko "Alice", ona odpowie.]}
child("Alice") => "My name is Alice, child of Mary"
To wszystko.
 121
Author: Tero Tolonen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-13 11:27:32

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

Oto zakończenie:

var a = 42;

function b() { return a; }
Tak. Pewnie używasz tego wiele razy dziennie.


Nie ma powodu, aby sądzić, że zamknięcia są skomplikowanym hack projektu do rozwiązania konkretnych problemów. Nie, zamykanie polega tylko na użyciu zmiennej, która pochodzi z wyższego zakresu z perspektywy miejsca, w którym funkcja została zadeklarowana (nie uruchomiona) .

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

 100
Author: floribon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-21 23:48:56

Przykład dla pierwszego punktu przez dlaliberte:

Zamknięcie jest tworzone nie tylko wtedy, gdy zwracasz wewnętrzną funkcję. W rzeczywistości funkcja zamykająca w ogóle nie musi zwracać. Możesz zamiast tego przypisać swoją wewnętrzną funkcję do zmiennej w zewnętrznym zakresie lub przekazać ją jako argument do innej funkcji, gdzie może być natychmiast użyta. Dlatego zamknięcie funkcji zamykającej prawdopodobnie już istnieje w czasie, gdy funkcja zamykająca została wywołana, ponieważ wszelkie wewnętrzne funkcja ma do niego dostęp zaraz po wywołaniu.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
 87
Author: someisaac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-16 02:39:35

Zamknięcie to miejsce, w którym funkcja wewnętrzna ma dostęp do zmiennych w swojej funkcji zewnętrznej. To prawdopodobnie najprostsze jednolinijkowe wyjaśnienie, jakie można uzyskać dla zamknięć.

 81
Author: Rakesh Pai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-24 11:10:56

Wiem, że rozwiązań jest już wiele, 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
 81
Author: Gerardo Lima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-09 11:32:47

Śpisz na noc i zapraszasz dana. Powiedz Danowi, żeby przyniósł jeden kontroler Xboxa.

Dan zaprasza Paula. Dan prosi Paula, by przyprowadził jednego kontrolera. Ilu kontrolerów przywieziono 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.");
 77
Author: StewShack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-20 15:16:26

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

  1. argumenty
  2. Locals (czyli ich zmienne lokalne i funkcje Lokalne)
  3. środowisko, które obejmuje:
    • globale, w tym DOM
    • wszystko w zewnętrznych funkcjach

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

Zauważ, że funkcje zewnętrzne nie są wymagane, choć oferują korzyści, o których tutaj nie dyskutuję. Poprzez dostęp do danych w jego środowisko, zamknięcie utrzymuje te dane przy życiu. W podklasie funkcji zewnętrznych/wewnętrznych funkcja zewnętrzna może tworzyć dane lokalne i ostatecznie wyjść, a jednak, jeśli jakakolwiek wewnętrzna funkcja(y) przetrwa po wyjściu funkcji zewnętrznej, to wewnętrzna funkcja(y) utrzymuje lokalne dane zewnętrznej funkcji przy życiu.

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

Wyobraź sobie, że zdarzenia przycisków Vote-Up I vote-Down są zaimplementowane jako closures, voteup_click i voteDown_click, które mają dostęp do zewnętrznych zmiennych isVotedUp i isVotedDown, które są definiowane globalnie. (Dla uproszczenia mam na myśli przyciski głosowania pytań Stoskoverflow, a nie tablicę przycisków głosowania odpowiedzi.)

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

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
}
[1]}wszystkie cztery z tych funkcji są zamknięciami, ponieważ wszystkie mają dostęp do swojego środowiska.
 73
Author: John Pick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-08 22:16:22

Autor zamknięcia wyjaśnił dość dobrze zamknięcia, wyjaśniając powody, dla których ich potrzebujemy, a także wyjaśniając środowisko leksykalne, które jest niezbędne do zrozumienia zamknięć.
Oto podsumowanie:

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

Tutaj wpisz opis obrazka

W tym przypadku interpreter znajduje zmienną w zewnętrzne LexicalEnvironment obiekt.

Proces składa się z dwóch kroki:

  1. Po pierwsze, gdy funkcja f jest tworzona, nie jest tworzona w pustym miejsce. Obecnie znajduje się tu obiekt leksykalny. W przypadku powyżej jest to okno (a jest niezdefiniowane w momencie funkcji kreacji).

Tutaj wpisz opis obrazka

Kiedy funkcja jest tworzona, otrzymuje ukrytą właściwość o nazwie [[Scope]], która odwołuje się do bieżącego środowiska leksykalnego.

Tutaj wpisz opis obrazka

Jeśli zmienna jest odczytywana, ale nie można jej nigdzie znaleźć, generowany jest błąd.

Funkcje zagnieżdżone

Funkcje mogą być zagnieżdżane jeden wewnątrz drugiego, tworząc łańcuch środowisk leksykalnych, który można również nazwać łańcuchem zakresu.

Tutaj wpisz opis obrazka

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

Zamknięcia

Funkcja zagnieżdżona może nadal żyć po zakończeniu funkcji zewnętrznej:

Tutaj wpisz opis obrazka

Zaznaczanie Środowisko leksykalne:

Tutaj wpisz opis obrazka

Jak widzimy, {[1] } jest właściwością w obiekcie user, więc nadal działa po zakończeniu użytkownika.

I jeśli pamiętasz, kiedy this.say jest tworzony ,to (jak każda funkcja) dostaje wewnętrzne odniesienie this.say.[[Scope]] do bieżącego środowiska leksykalnego. Tak więc środowisko leksykalne 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 jako zazwyczaj.

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

Podsumowując:

  1. wewnętrzna funkcja zachowuje odniesienie do zewnętrznej Środowisko leksykalne.
  2. wewnętrzna funkcja może uzyskać dostęp do zmiennych z niego w dowolnym momencie, nawet jeśli zewnętrzna funkcja jest zakończona.
  3. przeglądarka przechowuje leksykalne środowisko i wszystkie jego właściwości (zmienne) w pamięci, dopóki nie będzie wewnętrzna funkcja, która się do niej odwołuje.
To się nazywa zamknięcie.
 72
Author: Arvand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-14 20:51:20

Jako ojciec 6-latka, obecnie uczący małe dzieci (i względny początkujący w kodowaniu bez formalnego wykształcenia, więc korekty będą wymagane), myślę, że lekcja najlepiej trzymałaby się przez praktyczną zabawę. Jeśli 6-latek jest gotowy, aby zrozumieć, co to jest zamknięcie, to są na tyle dorośli, aby samemu spróbować. Proponuję wkleić kod do jsfiddle.net, trochę wyjaśniając i zostawiając ich samych, aby 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);

Instrukcje

DANE: dane są zbiorem faktów. Mogą to być liczby, słowa, pomiary, obserwacje, a nawet po prostu opisy rzeczy. Nie możesz jej dotknąć, powąchać ani posmakować. Możesz to zapisać, powiedzieć i usłyszeć. Możesz go użyć do [[23]}tworzenia dotykowego zapachu i smaku za pomocą komputera. Może być użyteczny przez komputer za pomocą kodu.

CODE: wszystkie powyższe zapisy nazywa się code . Jest napisany w JavaScript.

JAVASCRIPT: JavaScript jest językiem. Podobnie jak angielski, francuski czy Chiński są językami. Istnieje wiele języków, które są rozumiane przez komputery i inne procesory elektroniczne. Aby JavaScript był zrozumiały dla komputera, potrzebny jest interpreter. Wyobraź sobie, że nauczyciel, który mówi tylko po rosyjsku, przychodzi uczyć Twoją klasę w szkole. Kiedy nauczyciel mówi "все садятся", klasa nie zrozumie. Ale na szczęście masz rosyjskiego ucznia w klasie kto mówi wszystkim, że to oznacza "wszyscy siadać" - więc wszyscy tak robią. Klasa jest jak komputer, a Rosyjski uczeń jest tłumaczem. W języku JavaScript najpopularniejszym interpreterem 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, które możesz znać, Są Internet Explorer, Chrome, Firefox i Safari. Przeglądarka może zrozumieć JavaScript i powiedzieć komputerowi, co musi zrobić. JavaScript instrukcje nazywane są funkcjami.

Funkcja: funkcja w JavaScript jest jak Fabryka. To może być mała fabryka z tylko jedną maszyną w środku. Albo może zawierać wiele innych małych fabryk, każda z wieloma maszynami wykonującymi różne zadania. W prawdziwej fabryce ubrań możesz mieć ryzy tkaniny i szpulki nici, które wchodzą i wychodzą Koszulki i dżinsy. Nasza fabryka JavaScript przetwarza tylko dane, nie może szyć, wywiercić otworu ani stopić metalu. W naszej fabryce JavaScript data wchodzi i data wychodzi.

Wszystkie te dane brzmią trochę nudno, ale to naprawdę bardzo fajne; możemy mieć funkcję, która mówi robotowi, co zrobić na obiad. Powiedzmy, że zaproszę Ciebie i Twojego przyjaciela do mojego domu. Ty najbardziej lubisz kurze nóżki, ja lubię kiełbasy, twój przyjaciel zawsze chce tego, co ty, 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 dostarczyć funkcji dane o tym, co lubimy, funkcja może "rozmawiać" z lodówką, a funkcja może kontrolować robota.

Funkcja zwykle ma nazwę, nawiasy i szelki. Tak:

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

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

Nazwa: funkcja może być wywołana dowolnie wybranym słowem. Przykład "cookMeal" jest typowy w łączeniu dwóch słów i nadaniu drugiemu dużej litery na początku - ale nie jest to konieczne. Nie może mieć w sobie miejsca i nie może być liczbą samą w sobie.

Nawiasy: "nawiasy" lub () są skrzynką na drzwiach fabryki funkcji JavaScript lub skrzynką pocztową 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 takim przypadku ty wiedz, jakie dane musisz podać.

Szelki: "szelki", które wyglądają tak {} są przyciemnianymi szybami naszej fabryki. Z wnętrza fabryki widać na zewnątrz, ale z zewnątrz nie widać w środku.

PRZYKŁAD DŁUGIEGO KODU POWYŻEJ

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

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

Więc ta funkcja może mieć coś wspólnego ze śpiewaniem i może potrzebować pewnych danych o danej osobie. W środku jest instrukcja, żeby coś z tym zrobić data.

Teraz, po funkcji sing () , na końcu kodu znajduje się linia

var person="an old lady";

Zmienna: litery var oznaczają "zmienną". Zmienna jest jak koperta. Na Zewnątrz koperta ta jest oznaczona "osoba". Wewnątrz znajduje się kartka z informacjami, których potrzebuje nasza funkcja, kilka liter i spacji połączonych ze sobą jak kawałek sznurka (nazywa się to sznurkiem), które sprawiają, że fraza brzmi "starsza pani". Nasza koperta może zawierać inne rodzaje rzeczy, takie jak liczby (zwane liczbami całkowitymi), instrukcje (zwane funkcjami), listy (zwane tablicami ). Ponieważ zmienna jest zapisywana poza wszystkimi klamrami {} i ponieważ możesz widzieć przez przyciemnione okna, gdy jesteś w klamrach, zmienna ta może być widoczna z dowolnego miejsca w kodzie. Nazywamy to "zmienną globalną".

Zmienna globalna: person jest zmienną globalną, co oznacza, że jeśli zmienisz jej wartość z "an old lady" na " a młody człowiek", osoba będzie nadal młodym człowiekiem, dopóki nie zdecydujesz się zmienić go ponownie 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ę dewelopera przeglądarki i wpisz "person", aby zobaczyć, jaka jest ta wartość. Wpisz person="a young man", aby ją zmienić, a następnie wpisz ponownie "person", aby zobaczyć, że się zmieniła.

Po tym mamy linię

sing(person);

Ta linia nazywa function, as if it were calling a dog

"Come on sing , Come and get person !"

Gdy przeglądarka załaduje kod JavaScript i 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 śpiew. Zawiera zmienną o nazwie firstPart, która dotyczy śpiewu o osobie, która odnosi się do każdego z wersów pieśni: "był" + osoba + "który połknął". Jeśli wpiszesz firstPart do konsoli, nie otrzymasz odpowiedzi, ponieważ zmienna jest zamknięta w funkcji-przeglądarka nie widzi wewnątrz przyciemnionych okien szelek.

Zamknięcia: zamknięcia są mniejszymi funkcjami, które znajdują się wewnątrz dużej funkcji sing(). Małe fabryki wewnątrz dużej fabryki. Każdy z nich posiada własne aparaty ortodontyczne, co oznacza że zmienne wewnątrz nich nie mogą być widoczne z zewnątrz. Dlatego nazwy zmiennych ( i wynik ) mogą być powtarzane w zamknięciach, ale z różnymi wartościami. Jeśli wpiszesz nazwy tych zmiennych w oknie konsoli, nie otrzymasz ich wartości, ponieważ są one ukryte przez dwie warstwy przyciemnionych okien.

Wszyscy wiedzą, czym jest zmienna funkcji sing() o nazwie firstPart , ponieważ mogą widzieć z ich przyciemnionego okna.

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 funkcja sing ().

 58
Author: grateful,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-08 22:11:57

Ok, rozmawiając z 6-letnim dzieckiem, prawdopodobnie użyłbym następujących skojarzeń.

Wyobraź sobie-bawisz się ze swoimi młodszymi braćmi i siostrami w całym domu, poruszasz się ze swoimi zabawkami i przynosisz niektóre z nich do pokoju 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 już uzyskać dostępu do pozostawionych tam zabawek w bezpośredni sposób. Ale mógłbyś zapukać do drzwi i zapytać twój brat za te zabawki. To się nazywa Toy ' s closure; twój brat wymyślił to dla Ciebie, a teraz jest w zewnętrznym scope.

Porównaj z sytuacją, gdy drzwi zostały zamknięte przez przeciągnięcie i nikt w środku( ogólne wykonanie funkcji), a następnie jakiś lokalny pożar i spalić pokój (garbage collector:D), a następnie nowy pokój został zbudowany i teraz Można zostawić inne zabawki tam (nowa instancja funkcji), ale nigdy nie dostać te same zabawki, które zostały pozostawione w pierwszej instancji. miejsce.

Dla zaawansowanego dziecka chciałbym umieścić coś takiego jak poniżej. Nie jest idealny, 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 przez brata i bez względu na to, czy pokój jest zamknięty. Oto jsbin aby się nim bawić.

 52
Author: dmi3y,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-25 22:52:13

ODPOWIEDŹ dla sześciolatka (zakładając, że wie, czym jest funkcja, czym jest zmienna i jakie są DANE):

Funkcje mogą zwracać dane. Jednym z rodzajów danych, które można zwrócić z funkcji, jest inna funkcja. Gdy ta nowa funkcja zostanie zwrócona, wszystkie zmienne i argumenty użyte w funkcji, która ją utworzyła, nie znikają. Zamiast tego, że funkcja rodzica "zamyka."Innymi słowy, nic nie może zajrzeć do jego wnętrza i zobaczyć używanych zmiennych, z wyjątkiem funkcji, którą zwrócił. Ta nowa funkcja ma specjalną zdolność do spojrzenia wstecz wewnątrz funkcji, która ją utworzyła i zobaczenia danych wewnątrz niej.

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 na wyjaśnienie 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 jest w większym zakresie.

 48
Author: Stupid Stupid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-25 23:02:19

Funkcja w JavaScript nie jest tylko odniesieniem 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 (przechwyconych zmiennych). Takie dwuczęściowe funkcje nazywane są zamknięciami. Każda funkcja w JavaScript może być uznana za zamknięcie.

Zamknięcia są funkcjami ze stanem. Jest nieco podobny do "tego" w tym sensie, że" to " zapewnia również stan funkcji, ale funkcji i "this" są oddzielnymi obiektami ("this" jest tylko fantazyjnym parametrem, a jedynym sposobem na związanie go na stałe z funkcją jest utworzenie zamknięcia). Podczas gdy" this " I funkcja zawsze żyją osobno, funkcja nie może być oddzielona od jej zamknięcia, a język nie zapewnia dostępu do przechwyconych zmiennych.

Ponieważ wszystkie te zewnętrzne zmienne odwołujące się przez leksykalnie zagnieżdżoną funkcję są w rzeczywistości zmiennymi lokalnymi w łańcuchu jej leksykalnie zamykających się funkcji (zmienne globalne mogą być zakłada się, że są to zmienne lokalne jakiejś funkcji głównej), a każde pojedyncze wykonanie funkcji tworzy nowe instancje jej zmiennych lokalnych, wynika z tego, że każde wykonanie funkcji zwracającej (lub w inny sposób przenoszącej ją, np. rejestrującej ją jako wywołanie zwrotne) zagnieżdżona funkcja tworzy nowe zamknięcie (z własnym potencjalnie unikalnym zestawem odwołanych zmiennych nielokalnych, które reprezentują jej kontekst wykonania).

Należy również rozumieć, że zmienne lokalne w JavaScript są utworzone nie na ramce stosu, ale na stercie i zniszczone tylko wtedy, gdy nikt się do nich nie odwołuje. Gdy funkcja powraca, odwołania do jej zmiennych lokalnych są zmniejszane, ale nadal mogą być inne niż null, jeśli podczas bieżącego wykonywania stały się częścią zamknięcia i nadal są odwoływane przez jej leksykalnie zagnieżdżone funkcje (co może się zdarzyć tylko wtedy, gdy odwołania do tych zagnieżdżonych funkcji zostały zwrócone lub w inny sposób przeniesione do jakiegoś 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();
 47
Author: srgstm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-05 16:04:06

Być może trochę ponad wszystkie, ale najbardziej przedwczesny z sześciolatków, ale kilka przykładów, które pomogły w koncepcji zamknięcia w JavaScript kliknij Dla mnie.

Zamknięcie jest funkcją, która ma dostęp do zakresu innej funkcji (jej zmiennych i funkcji). Najprostszym sposobem na utworzenie zamknięcia jest funkcja wewnątrz funkcji; powodem jest to, że w JavaScript funkcja zawsze ma dostęp do zakresu swojej funkcji zawierającej.

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

outerFunction();

ALERT: małpa

W powyższym przykładzie wywołana jest funkcja outerFunction, która z kolei wywołuje funkcję innerFunction. Zauważ, jak outerVar jest dostępny dla innerFunction, o czym świadczy jego prawidłowe ostrzeganie o wartości outerVar.

Rozważ teraz, co następuje:

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

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ALERT: małpa

ReferenceToInnerFunction jest ustawiona na outerFunction (), która po prostu zwraca odwołanie do innerFunction. Po wywołaniu funkcji referenceToInnerFunction zwraca outerVar. Ponownie, jak wyżej, to pokazuje, że innerFunction ma dostęp do outerVar, zmiennej outerFunction. Co więcej, warto zauważyć, że zachowuje ten dostęp nawet po zakończeniu wykonywania outerFunction.

I tutaj robi się naprawdę ciekawie. Gdybyśmy mieli pozbyć się funkcji outerFunction, powiedzmy ustawić ją na null, mogłoby się wydawać, że referenceToInnerFunction utraciłaby 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: monkey

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

Powodem, dla którego referenceToInnerFunction może nadal uzyskać dostęp do wartości outerVar, jest to, że gdy zamknięcie zostało utworzone przez umieszczenie innerFunction wewnątrz outerFunction, innerFunction dodało odniesienie do zakresu outerFunction (jego zmiennych i funkcji) do łańcucha zakresu. Oznacza to, że innerFunction ma wskaźnik lub odniesienie do wszystkich zmiennych outerFunction, w tym outerVar. Tak więc nawet po zakończeniu wykonywania funkcji outerFunction, lub nawet po usunięciu lub ustawieniu na null, zmienne w jej zakresie, takie jak outerVar, pozostają w pamięci z powodu nierozstrzygniętego odniesienia do nich po stronie funkcji innerFunction, która została zwrócona do funkcji referenceToInnerFunction. Aby naprawdę uwolnić outervara i resztę zmiennych outerFunction z pamięci, będziesz musiał pozbyć się tego wyjątkowego odniesienie do nich, np. poprzez ustawienie referenceToInnerFunction NA null.

//////////

Dwie inne rzeczy o zamknięciach 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, kiedy tworzy się zamknięcie, zachowuje ono odniesienie do wszystkich zmiennych i funkcji funkcji zamykającej; nie ma możliwości wyboru i wyboru. I tak należy stosować zamknięcia oszczędnie lub przynajmniej ostrożnie, ponieważ mogą one być pamięciowe; wiele zmiennych może być przechowywanych w pamięci długo po zakończeniu wykonywania funkcji zawierającej.

 46
Author: Michael Dziedzic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-29 15:37:06

Po prostu skierowałbym je na stronę Mozilli . To najlepsze, najbardziej zwięzłe i proste wyjaśnienie podstaw zamknięcia i praktycznego użycia, jakie znalazłem. Jest to wysoce zalecane dla każdego, kto uczy się JavaScript.

I tak, nawet polecam to 6 - latkowi -- Jeśli 6 - latek uczy się o zamknięciach, to logiczne jest, że jest gotowy zrozumieć zwięzłe i proste wyjaśnienie zawarte w artykule.

 43
Author: mjmoody383,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-25 22:54:15