W skrócie Javascript Zamknięcia pozwolić funkcji na dostęp do zmiennej , która jest zadeklarowana w funkcji leksykalno-dominującej .
Zobaczmy bardziej szczegółowe wyjaśnienie. Aby zrozumieć zamknięcia, ważne jest zrozumienie, w jaki sposób JavaScript mierzy zmienne.
Zakresy
W JavaScript zakresy są definiowane za pomocą funkcji. Każda funkcja określa nowy zakres.
Rozważ następujący przykład;
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
wywoływanie f wydruków
hello
hello
2
Am I Accessible?
Rozważmy teraz przypadek, w którym mamy funkcję gzdefiniowaną w innej funkcji f.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Zadzwonimy fdo leksykalnego rodzica z g. Jak wyjaśniono wcześniej, mamy teraz 2 zakresy; zakres fi zakres g.
Ale jeden zakres jest „w obrębie” drugiego, więc czy zakres funkcji potomnej jest częścią zakresu funkcji rodzica? Co dzieje się ze zmiennymi zadeklarowanymi w zakresie funkcji nadrzędnej; czy będę mógł uzyskać do nich dostęp z zakresu funkcji potomnej? Właśnie tam wkraczają zamknięcia.
Domknięcia
W JavaScript funkcja gmoże nie tylko uzyskać dostęp do dowolnych zmiennych zadeklarowanych w zakresie, gale także uzyskać dostęp do dowolnych zmiennych zadeklarowanych w zakresie funkcji nadrzędnej f.
Rozważ następujące;
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
wywoływanie f wydruków
hello
undefined
Spójrzmy na linię console.log(foo);. W tym momencie jesteśmy w zakresie gi próbujemy uzyskać dostęp do zmiennej foozadeklarowanej w zakresie f. Ale jak wspomniano wcześniej, możemy uzyskać dostęp do dowolnej zmiennej zadeklarowanej w leksykalnej funkcji nadrzędnej, co ma miejsce tutaj; gjest leksykalnym rodzicem f. Dlatego hellojest drukowany.
Spójrzmy teraz na linię console.log(bar);. W tym momencie jesteśmy w zakresie fi próbujemy uzyskać dostęp do zmiennej barzadeklarowanej w zakresie g. barnie jest zadeklarowany w bieżącym zakresie, a funkcja gnie jest elementem nadrzędnym f, dlatego barjest niezdefiniowana
W rzeczywistości możemy również uzyskać dostęp do zmiennych zadeklarowanych w zakresie leksykalnej funkcji „dziadka”. Dlatego jeśli w funkcji będzie hzdefiniowana funkcjag
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
wtedy hbędzie w stanie uzyskać dostęp do wszystkich zmiennych zadeklarowanych w zakresie funkcji h, gorazf . Odbywa się to przy zamknięciach . W JavaScript zamknięcia pozwalają nam na dostęp do dowolnej zmiennej zadeklarowanej w leksykalnej funkcji nadrzędnej, w leksykalnej funkcji grand-grand, w leksykalnej funkcji grand-grand, itp. Można to postrzegać jako łańcuch zasięgu ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... do ostatniej funkcji nadrzędnej, która nie ma leksykalnego rodzica.
Obiekt okna
W rzeczywistości łańcuch nie kończy się na ostatniej funkcji nadrzędnej. Jest jeszcze jeden specjalny zakres; zakres globalny . Każda zmienna niezadeklarowana w funkcji jest uważana za zadeklarowaną w zasięgu globalnym. Globalny zasięg ma dwie specjalności;
- każda zmienna zadeklarowana w zasięgu globalnym jest dostępna wszędzie
- zmienne zadeklarowane w zakresie globalnym odpowiadają właściwościom
windowobiektu.
Dlatego istnieją dokładnie dwa sposoby deklarowania zmiennej foo w zasięgu globalnym; albo przez nie deklarowanie go w funkcji, ani przez ustawienie właściwości fooobiektu window.
Obie próby wykorzystują zamknięcia
Po przeczytaniu bardziej szczegółowego wyjaśnienia może się teraz okazać, że oba rozwiązania wykorzystują zamknięcia. Ale dla pewności zróbmy dowód.
Stwórzmy nowy język programowania; JavaScript bez zamykania. Jak sama nazwa wskazuje, JavaScript-No-Closure jest identyczny jak JavaScript, z tym wyjątkiem, że nie obsługuje Zamknięć.
Innymi słowy;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Dobra, zobaczmy, co się stanie z pierwszym rozwiązaniem z JavaScript-No-Closure;
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
dlatego zostanie wydrukowany undefined10 razy w JavaScript-No-Closure.
Dlatego pierwsze rozwiązanie wykorzystuje zamknięcie.
Spójrzmy na drugie rozwiązanie;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
dlatego zostanie wydrukowany undefined10 razy w JavaScript-No-Closure.
Oba rozwiązania wykorzystują zamknięcia.
Edycja: Zakłada się, że te 3 fragmenty kodu nie są zdefiniowane w zakresie globalnym. W przeciwnym razie zmienne fooi ibyłyby powiązane z windowobiektem, a zatem są dostępne przez windowobiekt zarówno w JavaScript, jak i JavaScript-No-Closure.