Co to jest „zamknięcie”?

432

Zadałem pytanie o curry i wspomniano o zamknięciach. Co to jest zamknięcie? Jak to się ma do curry?

Ben
źródło
22
Czym dokładnie jest zamknięcie ??? Niektóre odpowiedzi mówią, że zamknięcie jest funkcją. Niektórzy twierdzą, że to stos. Niektóre odpowiedzi mówią, że jest to „ukryta” wartość. W moim rozumieniu jest to funkcja + zamknięte zmienne.
Roland
3
Wyjaśnia, co to jest zamknięcie: stackoverflow.com/questions/4103750/...
dietbuddha
Zobacz także Co to jest zamknięcie? at softwareengineering.stackexchange
B12Toaster
Wyjaśnia, co to jest zamknięcie i typowy przypadek użycia: trungk18.com/experience/javascript-closure
Sasuke91

Odpowiedzi:

743

Zmienny zakres

Gdy deklarujesz zmienną lokalną, ta zmienna ma zasięg. Zasadniczo zmienne lokalne istnieją tylko w bloku lub funkcji, w której je deklarujesz.

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

Jeśli spróbuję uzyskać dostęp do zmiennej lokalnej, większość języków będzie jej szukała w bieżącym zakresie, a następnie w zakresach nadrzędnych, aż dotrą do zakresu głównego.

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

Kiedy wykonywany jest blok lub funkcja, jej zmienne lokalne nie są już potrzebne i zwykle są usuwane z pamięci.

Tak zwykle oczekujemy, że wszystko zadziała.

Zamknięcie jest trwałym zakresem zmiennej lokalnej

Zamknięcie jest trwałym zakresem, który zachowuje zmienne lokalne, nawet po wykonaniu kodu z tego bloku. Języki obsługujące zamknięcie (takie jak JavaScript, Swift i Ruby) pozwolą zachować referencję do zakresu (w tym jego zakresów nadrzędnych), nawet po zakończeniu wykonywania bloku, w którym zadeklarowano zmienne, pod warunkiem, że zachowasz referencję do tego bloku lub funkcji gdzieś.

Obiekt zasięgu i wszystkie jego zmienne lokalne są powiązane z funkcją i będą istnieć tak długo, jak długo ta funkcja będzie się utrzymywać.

To daje nam przenośność funkcji. Możemy spodziewać się, że wszystkie zmienne, które były w zakresie, gdy funkcja została zdefiniowana po raz pierwszy, nadal będą w zakresie, gdy później wywołamy funkcję, nawet jeśli wywołasz funkcję w zupełnie innym kontekście.

Na przykład

Oto naprawdę prosty przykład w JavaScript, który ilustruje tę kwestię:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

Tutaj zdefiniowałem funkcję w ramach funkcji. Funkcja wewnętrzna uzyskuje dostęp do wszystkich zmiennych lokalnych funkcji zewnętrznej, w tym a. Zmienna awchodzi w zakres funkcji wewnętrznej.

Zwykle po wyjściu funkcji wszystkie zmienne lokalne są zdmuchiwane. Jeśli jednak zwrócimy funkcję wewnętrzną i przypiszemy ją do zmiennej fnc, która będzie się utrzymywać po outerjej wyjściu, wszystkie zmienne, które były w zasięgu, gdy innerzostały zdefiniowane, również zostaną zachowane . Zmienna azostała zamknięta - znajduje się w zamknięciu.

Zauważ, że zmienna ajest całkowicie prywatna fnc. Jest to sposób tworzenia prywatnych zmiennych w funkcjonalnym języku programowania, takim jak JavaScript.

Jak możesz się domyślić, kiedy to nazywam fnc(), wypisuje wartość a, która wynosi „1”.

W języku bez zamknięcia zmienna abyłaby usuwana i wyrzucana po wyrzuceniu funkcji outer. Wywołanie fnc spowodowałoby błąd, ponieważ ajuż nie istnieje.

W JavaScript zmienna jest autrzymywana, ponieważ zakres zmiennej jest tworzony, gdy funkcja jest deklarowana po raz pierwszy i trwa tak długo, jak długo funkcja istnieje.

anależy do zakresu outer. Zakres innerma wskaźnik nadrzędny do zakresu outer. fncjest zmienną, która wskazuje inner. atrwa tak długo, jak fnctrwa. ajest w zamknięciu.

superluminarnych
źródło
116
Myślałem, że to całkiem dobry i łatwy do zrozumienia przykład.
user12345613,
16
Dzięki za niesamowite wytłumaczenie, widziałem wielu, ale to jest czas, kiedy naprawdę to dostałem.
Dimitar Dimitrov
2
Czy mogę podać przykład tego, jak to działa w bibliotece takiej jak JQuery, jak podano w drugim do ostatniego akapicie? Nie do końca to zrozumiałem.
DPM,
6
Cześć Jubbat, tak, otwórz jquery.js i spójrz na pierwszą linię. Zobaczysz, że funkcja jest otwarta. Teraz przejdź do końca, zobaczysz window.jQuery = window. $ = JQuery. Następnie funkcja jest zamykana i wykonywana samodzielnie. Masz teraz dostęp do funkcji $, która z kolei ma dostęp do innych funkcji zdefiniowanych w zamknięciu. Czy to jest odpowiedź na Twoje pytanie?
superluminarny
4
Najlepsze wyjaśnienie w sieci. O wiele prostsze niż myślałem
Mantis,
95

Podam przykład (w JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

Funkcja ta, makeCounter, polega na tym, że zwraca funkcję, którą nazwaliśmy x, która będzie zliczać o jedną za każdym razem, gdy zostanie wywołana. Ponieważ nie podajemy żadnych parametrów x, musi jakoś zapamiętać liczbę. Wie, gdzie ją znaleźć na podstawie tak zwanego zakresu leksykalnego - musi znaleźć miejsce, w którym jest zdefiniowane, aby znaleźć wartość. Ta „ukryta” wartość nazywana jest zamknięciem.

Oto mój przykład curry:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

Możesz zobaczyć, że kiedy wywołujesz add z parametrem a (czyli 3), ta wartość jest zawarta w zamknięciu zwróconej funkcji, którą definiujemy jako add3. W ten sposób, gdy wywołujemy add3, wie, gdzie znaleźć wartość do wykonania dodania.

Kyle Cronin
źródło
4
IDK, jakiego języka (prawdopodobnie F #) używałeś w powyższym języku. Czy możesz podać powyższy przykład w pseudokodzie? Trudno mi to zrozumieć.
użytkownik
1
@crucifiedsoul It's Scheme. ftp.cs.indiana.edu/pub/scheme-repository/doc/pubs/intro.txt
Kyle Cronin
3
@KyleCronin Świetny przykład, dzięki. P: Czy bardziej poprawne jest powiedzenie „ukryta wartość nazywa się zamknięciem”, czy też „funkcją, która ukrywa wartość, jest zamknięcie”? Lub „procesem ukrywania wartości jest zamknięcie”? Dzięki!
2
@RobertHume Dobre pytanie. Semantycznie termin „zamknięcie” jest nieco niejednoznaczny. Moją osobistą definicją jest to, że połączenie zarówno ukrytej wartości, jak i jej użycia przez funkcję zamykającą, stanowi zamknięcie.
Kyle Cronin
1
@KyleCronin Dzięki - w poniedziałek mam program średnioterminowy. :) Chciałem mieć w głowie solidną koncepcję „zamknięcia”. Dziękujemy za opublikowanie tej doskonałej odpowiedzi na pytanie OP!
58

Odpowiedź Kyle'a jest całkiem dobra. Myślę, że jedynym dodatkowym wyjaśnieniem jest to, że zamknięcie jest w zasadzie migawką stosu w punkcie, w którym tworzona jest funkcja lambda. Następnie, gdy funkcja jest ponownie wykonywana, stos jest przywracany do tego stanu przed wykonaniem funkcji. Zatem, jak wspomina Kyle, ta ukryta wartość ( count) jest dostępna, gdy funkcja lambda jest wykonywana.

Ben Childs
źródło
14
Nie chodzi tylko o stos - są zachowywane otaczające go zakresy leksykalne, niezależnie od tego, czy są przechowywane na stosie, czy na stosie (lub na obu).
Matt Fenwick,
38

Po pierwsze, w przeciwieństwie do tego, co mówi większość ludzi tutaj, zamknięcie nie jest funkcją ! Co to jest?
Jest to zestaw symboli zdefiniowanych w „otaczającym kontekście” funkcji (znanym jako jej środowisko ), które sprawiają, że jest to wyrażenie ZAMKNIĘTE (to znaczy wyrażenie, w którym każdy symbol jest zdefiniowany i ma wartość, dzięki czemu można go ocenić).

Na przykład, jeśli masz funkcję JavaScript:

function closed(x) {
  return x + 3;
}

jest to wyrażenie zamknięte, ponieważ wszystkie występujące w nim symbole są w nim zdefiniowane (ich znaczenie jest jasne), więc możesz je ocenić. Innymi słowy, jest samowystarczalny .

Ale jeśli masz taką funkcję:

function open(x) {
  return x*y + 3;
}

jest to wyrażenie otwarte, ponieważ znajdują się w nim symbole, które nie zostały w nim zdefiniowane. Mianowicie y. Patrząc na tę funkcję, nie możemy powiedzieć, co yjest i co to znaczy, nie znamy jej wartości, więc nie możemy ocenić tego wyrażenia. Tzn. Nie możemy wywołać tej funkcji, dopóki nie powiemy, co yma w niej oznaczać. To ysię nazywa wolny zmienna .

To ywymaga definicji, ale ta definicja nie jest częścią funkcji - jest zdefiniowana gdzie indziej, w jej „otaczającym kontekście” (znanym również jako środowisko ). Przynajmniej na to liczymy: P

Na przykład można go zdefiniować globalnie:

var y = 7;

function open(x) {
  return x*y + 3;
}

Lub może być zdefiniowany w funkcji, która go otacza:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

Część środowiska, która nadaje wolnym zmiennym w wyrażeniu swoje znaczenie, to zamknięcie . Nazywa się to w ten sposób, ponieważ zamienia wyrażenie otwarte w zamknięte , podając brakujące definicje dla wszystkich wolnych zmiennych , abyśmy mogli je ocenić.

W powyższym przykładzie funkcja wewnętrzna (której nie nadaliśmy nazwy, ponieważ jej nie potrzebowaliśmy) jest wyrażeniem otwartym, ponieważ zmienna yw niej jest wolna - jej definicja znajduje się poza funkcją, w funkcji, która ją otacza . Środowisko dla tej anonimowej funkcji jest zbiorem zmiennych:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Teraz zamknięcie jest tą częścią tego środowiska, która zamyka funkcję wewnętrzną, podając definicje dla wszystkich wolnych zmiennych . W naszym przypadku jedyną wolną zmienną w funkcji wewnętrznej było y, więc zamknięciem tej funkcji jest ten podzbiór jej środowiska:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Pozostałe dwa symbole zdefiniowane w środowisku nie są częścią zamknięcia tej funkcji, ponieważ nie wymaga ich uruchomienia. Nie są potrzebne do jego zamknięcia .

Więcej na temat teorii tutaj: https://stackoverflow.com/a/36878651/434562

Warto zauważyć, że w powyższym przykładzie funkcja otoki zwraca wartość wewnętrzną jako wartość. Moment, w którym wywołamy tę funkcję, może być zdalny w czasie od momentu jej zdefiniowania (lub utworzenia). W szczególności jego funkcja zawijania już nie działa, a jej parametrów, które były na stosie wywołań, już nie ma: P Stwarza to problem, ponieważ funkcja wewnętrzna musi ytam być, gdy jest wywoływana! Innymi słowy, wymaga zmiennych od swojego zamknięcia, aby jakoś przeżyć funkcję otoki i być tam, kiedy jest to potrzebne. Dlatego funkcja wewnętrzna musi wykonać migawkę tych zmiennych, które powodują jej zamknięcie i przechowywać je w bezpiecznym miejscu do późniejszego wykorzystania. (Gdzieś poza stosem wywołań.)

I dlatego ludzie często mylą pojęcie zamknięcia jako specjalnego rodzaju funkcji, która może wykonywać takie migawki zmiennych zewnętrznych, których używają, lub struktury danych używanej do przechowywania tych zmiennych na później. Ale mam nadzieję, że rozumiecie teraz, że nie są one samym zamknięciem - to tylko sposoby implementacji zamknięć w języku programowania lub mechanizmach językowych, które pozwalają, aby zmienne z zamknięcia funkcji były tam, gdzie jest to potrzebne. Istnieje wiele nieporozumień dotyczących zamknięć, które (niepotrzebnie) sprawiają, że ten temat jest znacznie bardziej zagmatwany i skomplikowany, niż jest w rzeczywistości.

SasQ
źródło
1
Analogią, która może pomóc początkującym w tym, jest zamknięcie wiąże wszystkie luźne końce , czyli to, co robi osoba, gdy dąży do zamknięcia (lub rozwiązuje wszystkie niezbędne odniesienia lub ...). Pomogło mi to myśleć w ten sposób: o)
Will Crawford
Przez lata czytałem wiele definicji zamknięcia, ale myślę, że jak dotąd jest to moja ulubiona. Wydaje mi się, że wszyscy mamy własny sposób mapowania mentalnego takich pojęć, a ten bardzo mija się z moimi.
Jason S.
29

Zamknięcie jest funkcją, która może odnosić się do stanu w innej funkcji. Na przykład w Pythonie używa zamknięcia „wewnętrznego”:

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1
John Millikin
źródło
23

Aby ułatwić zrozumienie zamknięć, przydatne może być zbadanie, w jaki sposób można je wdrożyć w języku proceduralnym. Wyjaśnienie to nastąpi po uproszczonej implementacji zamknięć w systemie.

Na początek muszę wprowadzić pojęcie przestrzeni nazw. Po wprowadzeniu polecenia do interpretera schematu musi on ocenić różne symbole w wyrażeniu i uzyskać ich wartość. Przykład:

(define x 3)

(define y 4)

(+ x y) returns 7

Wyrażenia definicyjne przechowują wartość 3 w miejscu dla x i wartość 4 w miejscu dla y. Następnie, gdy wywołujemy (+ xy), interpreter sprawdza wartości w przestrzeni nazw i jest w stanie wykonać operację i zwraca 7.

Jednak w schemacie istnieją wyrażenia, które pozwalają tymczasowo zastąpić wartość symbolu. Oto przykład:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

To, co robi słowo kluczowe let, wprowadza nową przestrzeń nazw z x jako wartością 5. Zauważysz, że nadal jest w stanie zobaczyć, że y wynosi 4, dzięki czemu suma jest zwracana do 9. Możesz także zobaczyć, że po zakończeniu wyrażenia x powraca do bycia 3. W tym sensie x został tymczasowo zamaskowany przez wartość lokalną.

Języki proceduralne i obiektowe mają podobną koncepcję. Za każdym razem, gdy deklarujesz zmienną w funkcji o tej samej nazwie co zmienna globalna, uzyskujesz ten sam efekt.

Jak moglibyśmy to wdrożyć? Prostą metodą jest lista połączona - głowa zawiera nową wartość, a ogon zawiera starą przestrzeń nazw. Kiedy musisz spojrzeć na symbol, zaczynasz od głowy i schodzisz po ogonie.

Przejdźmy teraz do implementacji pierwszorzędnych funkcji. Mniej więcej funkcja jest zestawem instrukcji do wykonania, gdy wywoływana jest funkcja, której wynikiem jest wartość zwracana. Kiedy czytamy funkcję, możemy przechowywać te instrukcje za scenami i uruchamiać je, gdy funkcja jest wywoływana.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

Definiujemy x na 3, a plus-x jako jego parametr, y plus wartość x. Na koniec wywołujemy plus-x w środowisku, w którym x zostało zamaskowane przez nowy x, ten miał wartość 5. Jeśli przechowujemy jedynie operację (+ xy) dla funkcji plus-x, ponieważ jesteśmy w kontekście przy x równym 5, zwracanym wynikiem będzie 9. To jest tak zwane dynamiczne określanie zakresu.

Jednak Scheme, Common Lisp i wiele innych języków ma tak zwany zakres leksykalny - oprócz przechowywania operacji (+ xy) przechowujemy również przestrzeń nazw w tym konkretnym punkcie. W ten sposób, patrząc na wartości, widzimy, że x, w tym kontekście, to naprawdę 3. To jest zamknięcie.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

Podsumowując, możemy użyć połączonej listy do przechowywania stanu przestrzeni nazw w momencie definicji funkcji, co pozwala nam na dostęp do zmiennych z obejmujących zakresy, a także daje nam możliwość lokalnego maskowania zmiennej bez wpływu na resztę program.

Kyle Cronin
źródło
okej, dzięki twojej odpowiedzi myślę, że w końcu mam pojęcie, o co chodzi w zamknięciu. Ale jest jedno wielkie pytanie: „możemy użyć połączonej listy do przechowywania stanu przestrzeni nazw w momencie definicji funkcji, umożliwiając nam dostęp do zmiennych, które w innym przypadku nie byłyby już w zasięgu”. Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer
@Laser: Przepraszam, to zdanie nie miało większego sensu, dlatego je zaktualizowałem. Mam nadzieję, że teraz ma to większy sens. Nie uważaj też połączonej listy za szczegół implementacji (ponieważ jest ona bardzo nieefektywna), ale jako prosty sposób na konceptualizację tego, jak można to zrobić.
Kyle Cronin
10

Oto prawdziwy przykład tego, dlaczego Closures kopie tyłek ... To jest prosto z mojego kodu JavaScript. Pozwól mi zilustrować.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

A oto, jak byś tego użył:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Teraz wyobraź sobie, że chcesz rozpocząć odtwarzanie z opóźnieniem, na przykład 5 sekund później po uruchomieniu tego fragmentu kodu. Cóż, to jest łatwe delayi to jest zamknięcie:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

Gdy wywołujesz za delaypomocą 5000ms, pierwszy fragment jest uruchamiany i przechowuje przekazane argumenty w swoim zamknięciu. Następnie 5 sekund później, kiedy setTimeoutnastąpi wywołanie zwrotne, zamknięcie nadal zachowuje te zmienne, dzięki czemu może wywoływać funkcję oryginalną z oryginalnymi parametrami.
Jest to rodzaj dekoracji curry lub funkcji.

Bez zamknięć musiałbyś w jakiś sposób utrzymać stan tych zmiennych poza funkcją, w ten sposób zaśmiecając kod poza funkcją z czymś, co logicznie należy do niej. Korzystanie z zamknięć może znacznie poprawić jakość i czytelność kodu.

adamJLev
źródło
1
Należy zauważyć, że rozszerzenie obiektów języka lub hosta jest ogólnie uważane za coś złego, ponieważ są one częścią globalnej przestrzeni nazw
Jon Cooke
9

Funkcje nie zawierające wolnych zmiennych nazywane są funkcjami czystymi.

Funkcje zawierające jedną lub więcej wolnych zmiennych nazywane są zamknięciami.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

soundyogi
źródło
Dlaczego jest to lekceważone? W rzeczywistości jest to znacznie bardziej „na właściwej ścieżce” z tym rozróżnieniem na zmienne swobodne i zmienne powiązane oraz funkcje czyste / zamknięte i funkcje nieczyste / otwarte, niż większość innych tutaj nieokreślonych odpowiedzi: P (dyskontowanie za mylące zamykanie z funkcjami zamknięte).
SasQ
Naprawdę nie mam pojęcia. Dlatego StackOverflow jest do bani. Wystarczy spojrzeć na źródło mojej odpowiedzi. Kto mógłby się z tym kłócić?
soundyogi 27.04.16
SO nie jest do kitu i nigdy nie słyszałem o „zmiennej zmiennej”
Kai
Trudno mówić o zamknięciach, nie wspominając o wolnych zmiennych. Poszukaj ich. Standardowa terminologia CS.
ComDubh
„Funkcje zawierające jedną lub więcej wolnych zmiennych nazywane są zamknięciami” nie są jednak poprawną definicją - zamknięcia są zawsze obiektami pierwszej klasy.
ComDubh
7

tl; dr

Zamknięcie jest funkcją, a jej zakres jest przypisany (lub użyty jako) zmienna. Zatem zamknięcie nazwy: zakres i funkcja są zamknięte i używane tak jak każdy inny byt.

Szczegółowe wyjaśnienie stylu Wikipedii

Według Wikipedii zamknięcie to:

Techniki implementacji leksykalnego wiązania nazw w językach z pierwszorzędnymi funkcjami.

Co to znaczy? Przyjrzyjmy się niektórym definicjom.

Wyjaśnię zamknięcia i inne powiązane definicje za pomocą tego przykładu:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

Funkcje pierwszej klasy

Zasadniczo oznacza to, że możemy korzystać z funkcji tak jak z każdego innego bytu . Możemy je modyfikować, przekazywać jako argumenty, zwracać z funkcji lub przypisywać do zmiennych. Technicznie rzecz biorąc, są pierwszorzędnymi obywatelami , stąd nazwa: pierwszorzędne funkcje.

W powyższym przykładzie startAtzwraca ( anonimową ) funkcję, której funkcja zostanie przypisana do closure1i closure2. Tak więc, jak widzisz, JavaScript traktuje funkcje tak jak wszystkie inne podmioty (obywatele pierwszej klasy).

Wiązanie nazwy

Powiązanie nazwy polega na ustaleniu, do jakich danych odwołuje się zmienna (identyfikator) . Zakres jest tutaj naprawdę ważny, ponieważ to on decyduje o sposobie rozwiązania powiązania.

W powyższym przykładzie:

  • W zakresie w funkcji wewnętrznej anonymous, yjest związany 3.
  • W startAtzakresie xjest związany 1lub 5(w zależności od zamknięcia).

Wewnątrz zakresu funkcji anonimowej xnie jest powiązana z żadną wartością, dlatego należy ją rozwiązać w górnym startAtzakresie.

Zakres leksykalny

Jak mówi Wikipedia , zakres:

Jest regionem programu komputerowego, w którym powiązanie jest ważne: gdzie nazwa może być użyta w odniesieniu do encji .

Istnieją dwie techniki:

  • Zasięg leksykalny (statyczny): definicja zmiennej jest rozwiązywana przez przeszukanie jej zawierającego bloku lub funkcji, a jeśli to się nie powiedzie, przeszukanie zewnętrznego bloku zawierającego i tak dalej.
  • Dynamiczne określanie zakresu: Przeszukiwana jest funkcja wywoływania, a następnie funkcja, która wywołała tę funkcję wywoływania, i tak dalej, zwiększając stos wywołań.

Aby uzyskać więcej wyjaśnień, sprawdź to pytanie i zajrzyj na Wikipedię .

W powyższym przykładzie widzimy, że JavaScript ma zasięg leksykalny, ponieważ po xrozwiązaniu powiązanie jest przeszukiwane w górnym startAtzakresie, w oparciu o kod źródłowy (anonimowa funkcja szukająca x jest zdefiniowana wewnątrz startAt) i nie opiera się na stosie wywołań, sposobie (zasięgu gdzie) funkcja została wywołana.

Zawijanie (zamykanie) w górę

W naszym przykładzie, gdy wywołamy startAt, zwróci funkcję (pierwszej klasy), do której zostanie przypisane, closure1a closure2zatem zostanie utworzone zamknięcie, ponieważ przekazane zmienne 1i 5zostaną zapisane w startAtzakresie, który zostanie dołączony do zwróconego funkcja anonimowa. Kiedy wywołujemy tę anonimową funkcję za pomocą closure1i za closure2pomocą tego samego argumentu ( 3), wartość yzostanie znaleziona natychmiast (ponieważ jest to parametr tej funkcji), ale xnie jest ograniczona zakresem funkcji anonimowej, więc rozdzielczość jest kontynuowana w (leksykalnie) górny zakres funkcji (który został zapisany w zamknięciu), w którym xstwierdzono, że jest związany z jednym 1lub drugim5. Teraz wiemy wszystko o podsumowaniu, więc wynik można zwrócić, a następnie wydrukować.

Teraz powinieneś zrozumieć zamknięcia i jak się zachowują, co jest podstawową częścią JavaScript.

Curry

Aha, a także nauczyłeś się, o co chodzi w curry : używasz funkcji (zamknięć) do przekazywania każdego argumentu operacji zamiast używania jednej funkcji z wieloma parametrami.

totymedli
źródło
5

Zamknięcie to funkcja JavaScript, w której funkcja ma dostęp do własnych zmiennych zakresu, dostęp do zewnętrznych zmiennych funkcji i dostęp do zmiennych globalnych.

Zamknięcie ma dostęp do swojego zakresu funkcji zewnętrznej nawet po powrocie funkcji zewnętrznej. Oznacza to, że zamknięcie może zapamiętać i uzyskać dostęp do zmiennych i argumentów swojej funkcji zewnętrznej, nawet po zakończeniu funkcji.

Funkcja wewnętrzna może uzyskać dostęp do zmiennych zdefiniowanych we własnym zakresie, zakresie funkcji zewnętrznej i zasięgu globalnym. A funkcja zewnętrzna może uzyskać dostęp do zmiennej zdefiniowanej we własnym zakresie i zakresie globalnym.

Przykład zamknięcia :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

Wyjście będzie równe 20, która będzie sumą jego zmiennej własnej, wartości zmiennej zewnętrznej i wartości zmiennej globalnej.

rahul sharma
źródło
4

W normalnej sytuacji zmienne są powiązane regułą określania zakresu: zmienne lokalne działają tylko w obrębie zdefiniowanej funkcji. Zamknięcie jest sposobem na tymczasowe złamanie tej zasady dla wygody.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

w powyższym kodzie lambda(|n| a_thing * n} jest zamknięcie, ponieważ a_thingodwołuje się do niego lambda (anonimowy twórca funkcji).

Teraz, jeśli umieścisz wynikową anonimową funkcję w zmiennej funkcji.

foo = n_times(4)

foo złamie normalną zasadę określania zakresu i zacznie używać 4 wewnętrznie.

foo.call(3)

zwraca 12.

Eugene Yokota
źródło
2

Krótko mówiąc, wskaźnik funkcji jest tylko wskaźnikiem do lokalizacji w bazie kodu programu (jak licznik programu). Natomiast zamknięcie = wskaźnik funkcji + ramka stosu .

.

RoboAlex
źródło
1

• Zamknięcie jest podprogramem i środowiskiem odniesienia, w którym zostało zdefiniowane

- Środowisko odniesienia jest potrzebne, jeśli podprogram można wywołać z dowolnego dowolnego miejsca w programie

- Język o statycznym zasięgu, który nie pozwala na zagnieżdżanie podprogramów, nie wymaga zamykania

- Zamknięcia są potrzebne tylko wtedy, gdy podprogram może uzyskać dostęp do zmiennych w zakresach zagnieżdżania i można go wywołać z dowolnego miejsca

- W celu obsługi zamknięć, implementacja może wymagać zapewnienia nieograniczonego zasięgu dla niektórych zmiennych (ponieważ podprogram może uzyskać dostęp do zmiennej nielokalnej, która normalnie nie jest już żywa)

Przykład

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);
BoraKurucu
źródło
0

Oto kolejny przykład z życia i używanie popularnego w grach języka skryptowego - Lua. Musiałem nieznacznie zmienić sposób działania funkcji biblioteki, aby uniknąć problemu z niedostępnością standardowego wejścia.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

Wartość old_dofile znika, gdy ten blok kodu kończy swój zasięg (ponieważ jest lokalny), jednak wartość została zamknięta w zamknięciu, więc nowa przedefiniowana funkcja dofile może uzyskać do niej dostęp, a raczej kopię przechowywaną wraz z funkcją jako „upvalue”.

Nigel Atkinson
źródło
0

Z Lua.org :

Kiedy funkcja jest zapisana w innej funkcji, ma pełny dostęp do zmiennych lokalnych z funkcji otaczającej; ta funkcja nosi nazwę zakresu leksykalnego. Choć może to zabrzmieć oczywisto, tak nie jest. Zasięg leksykalny oraz pierwszorzędne funkcje to potężna koncepcja w języku programowania, ale niewiele języków obsługuje tę koncepcję.

użytkownik5731811
źródło
0

Jeśli jesteś ze świata Java, możesz porównać zamknięcie z funkcją członka klasy. Spójrz na ten przykład

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

Funkcja gjest zamknięciem: gzamyka się a. gMożna ją więc porównać z funkcją składową, amożna porównać z polem klasy i funkcją fz klasą.

rev er er
źródło
0

Zamknięcia Ilekroć mamy funkcję zdefiniowaną w innej funkcji, funkcja wewnętrzna ma dostęp do zmiennych zadeklarowanych w funkcji zewnętrznej. Zamknięcia najlepiej wyjaśnić przykładami. Na listingu 2-18 widać, że funkcja wewnętrzna ma dostęp do zmiennej (variableInOuterFunction) z zewnętrznego zakresu. Zmienne w funkcji zewnętrznej zostały zamknięte (lub powiązane) funkcją wewnętrzną. Stąd termin zamknięcia. Sama koncepcja jest dość prosta i dość intuicyjna.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

źródło: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf

Shohan
źródło
0

Spójrz poniżej kodu, aby zrozumieć głębiej zamknięcie:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

Tutaj, co zostanie wydrukowane? 0,1,2,3,4nie to będzie5,5,5,5,5 tak z powodu zamknięcia

Jak to rozwiąże? Odpowiedź jest poniżej:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Pozwólcie, że wyjaśnię: kiedy funkcja nie utworzyła nic, dopóki nie wywołała pętli tak w pierwszym kodzie wywołanym 5 razy, ale nie została wywołana natychmiast, więc wywołana tj. Po 1 sekundzie, a także jest asynchroniczna, więc przed zakończeniem pętli i zapisaniem wartości 5 w var i i na końcu wykonaj setTimeout funkcję pięć razy i wydrukuj5,5,5,5,5

Tutaj jak to rozwiązać za pomocą IIFE, tzn. Natychmiastowego wywołania wyrażenia funkcji

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

Aby uzyskać więcej informacji, zapoznaj się z kontekstem wykonania, aby zrozumieć zamknięcie.

  • Jest jeszcze jedno rozwiązanie tego problemu za pomocą let (funkcja ES6), ale pod maską powyżej działa ta funkcja

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> Więcej wyjaśnień:

W pamięci, gdy dla pętli wykonaj obraz, wykonaj jak poniżej:

Pętla 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Pętla 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Pętla 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Pętla 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Pętla 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Tutaj nie jest wykonywane, a następnie po zakończeniu pętli var i zapisuje wartość 5 w pamięci, ale jej zasięg jest zawsze widoczny w funkcji potomnej, więc gdy funkcja jest wykonywana wewnątrz setTimeout stronę pięć razy, drukuje5,5,5,5,5

Aby rozwiązać ten problem, użyj IIFE, jak wyjaśniono powyżej.

arun
źródło
dzięki za odpowiedź. byłoby bardziej czytelne, gdyby oddzielić kod od wyjaśnienia. (nie
wcinaj
0

Currying: Pozwala ci częściowo ocenić funkcję, przekazując tylko podzbiór jej argumentów. Rozważ to:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

Zamknięcie: Zamknięcie jest niczym innym jak dostępem do zmiennej poza zakresem funkcji. Ważne jest, aby pamiętać, że funkcja wewnątrz funkcji lub funkcja zagnieżdżona nie jest zamknięciem. Zamknięcia są zawsze używane, gdy trzeba uzyskać dostęp do zmiennych poza zakresem funkcji.

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21
użytkownik11335201
źródło
0

Zamknięcie jest bardzo łatwe. Możemy to rozważyć w następujący sposób: Zamknięcie = funkcja + jego środowisko leksykalne

Rozważ następującą funkcję:

function init() {
    var name = “Mozilla”;
}

Jakie będzie zamknięcie w powyższej sprawie? Funkcja init () i zmienne w jej środowisku leksykalnym, tj. Name. Zamknięcie = init () + nazwa

Rozważ inną funkcję:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

Jakie będą tutaj zamknięcia? Funkcja wewnętrzna może uzyskać dostęp do zmiennych funkcji zewnętrznej. displayName () może uzyskać dostęp do nazwy zmiennej zadeklarowanej w funkcji nadrzędnej, init (). Jednak te same zmienne lokalne w displayName () zostaną użyte, jeśli istnieją.

Zamknięcie 1: funkcja init + (nazwa zmiennej + funkcja displayName ()) -> zakres leksykalny

Zamknięcie 2: funkcja displayName + (nazwa zmiennej) -> zakres leksykalny

Rumel
źródło
0

Zamknięcia zapewniają JavaScript ze stanem.

Stan w programowaniu oznacza po prostu zapamiętywanie rzeczy.

Przykład

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

W powyższym przypadku stan jest przechowywany w zmiennej „a”. Następnie dodajemy 1 do „a” kilka razy. Możemy to zrobić tylko dlatego, że jesteśmy w stanie „zapamiętać” wartość. Właściciel stanu „a” przechowuje tę wartość w pamięci.

Często w językach programowania chcesz śledzić rzeczy, zapamiętywać informacje i uzyskiwać do nich dostęp później.

W innych językach jest to zwykle realizowane za pomocą klas. Klasa, podobnie jak zmienne, śledzi swój stan. Z kolei instancje tej klasy również mają w sobie stan. Stan oznacza po prostu informacje, które możesz przechowywać i odzyskiwać później.

Przykład

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

Jak uzyskać dostęp do „wagi” z poziomu metody „renderowania”? Cóż, dzięki państwu. Każde wystąpienie klasy Chleb może nadać swój własny ciężar, czytając go z „stanu”, miejsca w pamięci, w którym moglibyśmy przechowywać te informacje.

Teraz JavaScript jest bardzo unikalnym językiem, który historycznie nie ma klas (teraz ma, ale pod maską są tylko funkcje i zmienne), więc Zamknięcia umożliwiają JavaScript zapamiętanie rzeczy i dostęp do nich później.

Przykład

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

Powyższy przykład osiągnął cel „utrzymania stanu” za pomocą zmiennej. To jest świetne! Ma to jednak tę wadę, że zmienna (właściciel „stanu”) jest teraz widoczna. Możemy zrobić lepiej. Możemy użyć Zamknięć.

Przykład

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

To jest fantastyczne.

Teraz nasza funkcja „count” może liczyć. Jest w stanie to zrobić, ponieważ może „zatrzymać” stan. W tym przypadku stanem jest zmienna „n”. Ta zmienna jest teraz zamknięta. Zamknięte w czasie i przestrzeni. Z czasem, ponieważ nigdy nie będziesz w stanie go odzyskać, zmienić, przypisać wartość lub bezpośrednio z nią współdziałać. W przestrzeni kosmicznej, ponieważ jest geograficznie zagnieżdżony w funkcji „countGenerator”.

Dlaczego to jest fantastyczne? Ponieważ bez angażowania innych wyrafinowanych i skomplikowanych narzędzi (np. Klas, metod, instancji itp.) Jesteśmy w stanie 1. ukryć 2. kontrolować na odległość

Kryjemy stan, zmienną „n”, co czyni ją zmienną prywatną! Stworzyliśmy również interfejs API, który może kontrolować tę zmienną w określony sposób. W szczególności możemy wywołać API w taki sposób, aby „count ()”, a to dodaje 1 do „n” z „odległości”. W żaden sposób, kształt ani forma nikt nie będzie mógł uzyskać dostępu do „n”, chyba że za pośrednictwem interfejsu API.

JavaScript jest naprawdę niesamowity w swojej prostocie.

Zamknięcia są w dużej mierze przyczyną tego.

Claudio
źródło
0

Prosty przykład w Groovy w celach informacyjnych:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
GraceMeng
źródło