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ę g
zdefiniowaną 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 f
do leksykalnego rodzica z g
. Jak wyjaśniono wcześniej, mamy teraz 2 zakresy; zakres f
i 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 g
może nie tylko uzyskać dostęp do dowolnych zmiennych zadeklarowanych w zakresie, g
ale 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 g
i próbujemy uzyskać dostęp do zmiennej foo
zadeklarowanej 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; g
jest leksykalnym rodzicem f
. Dlatego hello
jest drukowany.
Spójrzmy teraz na linię console.log(bar);
. W tym momencie jesteśmy w zakresie f
i próbujemy uzyskać dostęp do zmiennej bar
zadeklarowanej w zakresie g
. bar
nie jest zadeklarowany w bieżącym zakresie, a funkcja g
nie jest elementem nadrzędnym f
, dlatego bar
jest 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 h
zdefiniowana 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 h
będzie w stanie uzyskać dostęp do wszystkich zmiennych zadeklarowanych w zakresie funkcji h
, g
orazf
. 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
window
obiektu.
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 foo
obiektu 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 undefined
10 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 undefined
10 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 foo
i i
byłyby powiązane z window
obiektem, a zatem są dostępne przez window
obiekt zarówno w JavaScript, jak i JavaScript-No-Closure.