In poche parole Javascript Closures permettono una funzione per accedere a una variabile che viene dichiarata in una funzione lessicale-genitore .
Vediamo una spiegazione più dettagliata. Per comprendere le chiusure è importante comprendere come le variabili degli ambiti JavaScript.
Scopes
In JavaScript gli ambiti sono definiti con funzioni. Ogni funzione definisce un nuovo ambito.
Considera il seguente esempio;
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
chiamando f stampe
hello
hello
2
Am I Accessible?
Consideriamo ora il caso in cui abbiamo una funzione g
definita all'interno di un'altra funzione f
.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Chiameremo f
il genitore lessicale di g
. Come spiegato prima, ora abbiamo 2 ambiti; l'ambito f
e l'ambitog
.
Ma un ambito è "all'interno" dell'altro ambito, quindi l'ambito della funzione figlio fa parte dell'ambito della funzione padre? Cosa succede con le variabili dichiarate nell'ambito della funzione genitore; sarò in grado di accedervi dall'ambito della funzione figlio? È esattamente qui che entrano in gioco le chiusure.
chiusure
In JavaScript la funzione g
non può solo accedere a tutte le variabili dichiarate nell'ambito g
ma anche a tutte le variabili dichiarate nell'ambito della funzione genitore f
.
Valuta di seguire;
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
chiamando f stampe
hello
undefined
Diamo un'occhiata alla linea console.log(foo);
. A questo punto siamo nell'ambito g
e cerchiamo di accedere alla variabile foo
dichiarata nell'ambito f
. Ma come detto prima possiamo accedere a qualsiasi variabile dichiarata in una funzione parentale lessicale che è il caso qui; g
è il genitore lessicale di f
. Pertanto hello
viene stampato.
Vediamo ora la linea console.log(bar);
. A questo punto siamo nell'ambito f
e cerchiamo di accedere alla variabile bar
dichiarata nell'ambito g
. bar
non è dichiarato nell'ambito corrente e la funzione g
non è il genitore di f
, quindi bar
non è definita
In realtà possiamo anche accedere alle variabili dichiarate nell'ambito di una funzione lessicale "nonna". Pertanto, se ci fosse una funzione h
definita all'interno della funzioneg
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
quindi h
sarebbe in grado di accedere a tutte le variabili dichiarate nel campo di applicazione della funzione h
, g
e f
. Questo viene fatto con le chiusure . Nelle chiusure JavaScript ci consente di accedere a qualsiasi variabile dichiarata nella funzione lessicale parent, nella funzione lessicale grand parent, nella funzione lessical grand-grand parent, ecc. Ciò può essere visto come una catena di scope ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
fino all'ultima funzione padre che non ha genitori lessicali.
L'oggetto finestra
In realtà la catena non si ferma all'ultima funzione genitore. C'è un altro ambito speciale; il ambito globale . Ogni variabile non dichiarata in una funzione è considerata dichiarata nell'ambito globale. L'ambito globale ha due specialità;
- ogni variabile dichiarata nell'ambito globale è accessibile ovunque
- le variabili dichiarate nell'ambito globale corrispondono alle proprietà di
window
dell'oggetto.
Pertanto ci sono esattamente due modi per dichiarare una variabile foo
nell'ambito globale; o non dichiarandolo in una funzione o impostando la proprietàfoo
dell'oggetto finestra.
Entrambi i tentativi utilizzano chiusure
Ora che hai letto una spiegazione più dettagliata, ora può essere evidente che entrambe le soluzioni utilizzano chiusure. Ma per essere sicuri, facciamo una prova.
Creiamo un nuovo linguaggio di programmazione; JavaScript-No-chiusura. Come suggerisce il nome, JavaScript-No-Closure è identico a JavaScript, tranne per il fatto che non supporta le chiusure.
In altre parole;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Bene, vediamo cosa succede con la prima soluzione con 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)
})();
}
pertanto questo verrà stampato undefined
10 volte in JavaScript-No-Closure.
Quindi la prima soluzione utilizza la chiusura.
Diamo un'occhiata alla seconda soluzione;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
pertanto questo verrà stampato undefined
10 volte in JavaScript-No-Closure.
Entrambe le soluzioni utilizzano chiusure.
Modifica: si presume che questi 3 frammenti di codice non siano definiti nell'ambito globale. Altrimenti le variabili foo
e i
sarebbero legate window
all'oggetto e quindi accessibili attraverso l' window
oggetto sia in JavaScript che in JavaScript-No-Closure.