Zadałem pytanie o curry i wspomniano o zamknięciach. Co to jest zamknięcie? Jak to się ma do curry?
Zadałem pytanie o curry i wspomniano o zamknięciach. Co to jest zamknięcie? Jak to się ma do curry?
Odpowiedzi:
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, 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.
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 a
wchodzi 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 outer
jej wyjściu, wszystkie zmienne, które były w zasięgu, gdy inner
zostały zdefiniowane, również zostaną zachowane . Zmienna a
została zamknięta - znajduje się w zamknięciu.
Zauważ, że zmienna a
jest 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 a
byłaby usuwana i wyrzucana po wyrzuceniu funkcji outer
. Wywołanie fnc spowodowałoby błąd, ponieważ a
już nie istnieje.
W JavaScript zmienna jest a
utrzymywana, ponieważ zakres zmiennej jest tworzony, gdy funkcja jest deklarowana po raz pierwszy i trwa tak długo, jak długo funkcja istnieje.
a
należy do zakresu outer
. Zakres inner
ma wskaźnik nadrzędny do zakresu outer
. fnc
jest zmienną, która wskazuje inner
. a
trwa tak długo, jak fnc
trwa. a
jest w zamknięciu.
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.
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.
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 y
jest 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 y
ma w niej oznaczać. To y
się nazywa wolny zmienna .
To y
wymaga 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 y
w 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 y
tam 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.
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
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.
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?
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 delay
i to jest zamknięcie:
startPlayback.delay(5000, someTrack);
// Keep going, do other things
Gdy wywołujesz za delay
pomocą 5000
ms, pierwszy fragment jest uruchamiany i przechowuje przekazane argumenty w swoim zamknięciu. Następnie 5 sekund później, kiedy setTimeout
nastą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.
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
}
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.
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)
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 startAt
zwraca ( anonimową ) funkcję, której funkcja zostanie przypisana do closure1
i closure2
. Tak więc, jak widzisz, JavaScript traktuje funkcje tak jak wszystkie inne podmioty (obywatele pierwszej klasy).
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:
y
jest związany 3
.startAt
zakresie x
jest związany 1
lub 5
(w zależności od zamknięcia).Wewnątrz zakresu funkcji anonimowej x
nie jest powiązana z żadną wartością, dlatego należy ją rozwiązać w górnym startAt
zakresie.
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:
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 x
rozwiązaniu powiązanie jest przeszukiwane w górnym startAt
zakresie, 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.
W naszym przykładzie, gdy wywołamy startAt
, zwróci funkcję (pierwszej klasy), do której zostanie przypisane, closure1
a closure2
zatem zostanie utworzone zamknięcie, ponieważ przekazane zmienne 1
i 5
zostaną zapisane w startAt
zakresie, który zostanie dołączony do zwróconego funkcja anonimowa. Kiedy wywołujemy tę anonimową funkcję za pomocą closure1
i za closure2
pomocą tego samego argumentu ( 3
), wartość y
zostanie znaleziona natychmiast (ponieważ jest to parametr tej funkcji), ale x
nie 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 x
stwierdzono, że jest związany z jednym 1
lub 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.
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.
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.
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_thing
odwoł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.
• 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 />″);
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”.
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ę.
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 g
jest zamknięciem: g
zamyka się a
. g
Można ją więc porównać z funkcją składową, a
można porównać z polem klasy i funkcją f
z klasą.
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
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,4
nie 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.
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
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
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
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.
Zamknięcia są w dużej mierze przyczyną tego.