Le chiusure in JavaScript sono uno di quei concetti che molti fanno fatica a capire. Nel seguente articolo, spiegherò in termini chiari cos’è una chiusura, e porterò il punto a casa usando semplici esempi di codice.
Una chiusura è una caratteristica in JavaScript in cui una funzione interna ha accesso alle variabili della funzione esterna (che racchiude) – una catena di scope.
La chiusura ha tre catene di scope:
- ha accesso al proprio scope – le variabili definite tra le sue parentesi graffe
- ha accesso alle variabili della funzione esterna
- ha accesso alle variabili globali
Per i non iniziati, questa definizione potrebbe sembrare solo un sacco di gergo!
Ma cos’è veramente una chiusura?
Guardiamo un semplice esempio di chiusura in JavaScript:
function outer() { var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
Qui abbiamo due funzioni:
- una funzione esterna
outer
che ha una variabileb
, e restituisce lainner
funzione - una funzione interna
inner
che ha la sua variabile chiamataa
, e accede ad una variabileouter
variabileb
, all’interno del suo corpo di funzione
Lo scopo della variabile b
è limitato alla funzione outer
, e lo scopo della variabile a
è limitato alla funzione inner
.
Invochiamo ora la funzione outer()
e memorizziamo il risultato della funzione outer()
in una variabile X
. Invochiamo quindi la funzione outer()
una seconda volta e memorizziamola nella variabile Y
.
Vediamo passo dopo passo cosa succede quando la funzione outer()
viene invocata per la prima volta:
- La variabile
b
viene creata, il suo ambito è limitato alla funzioneouter()
e il suo valore viene impostato a10
. - La prossima linea è una dichiarazione di funzione, quindi niente da eseguire.
- Nell’ultima riga,
return inner
cerca una variabile chiamatainner
, trova che questa variabileinner
è in realtà una funzione, e quindi restituisce l’intero corpo della funzioneinner
. - Il contenuto restituito dalla dichiarazione di ritorno è memorizzato in
X
.
Quindi,X
memorizzerà quanto segue:function inner() {
var a=20;
console.log(a+b);
} - La funzione
outer()
termina l’esecuzione, e tutte le variabili nello scope diouter()
ora non esistono più.
Questa ultima parte è importante da capire. Una volta che una funzione completa la sua esecuzione, tutte le variabili che sono state definite all’interno dello scope della funzione cessano di esistere.
La durata di una variabile definita all’interno di una funzione è la durata dell’esecuzione della funzione.
Questo significa che in console.log(a+b)
, la variabile b
esiste solo durante l’esecuzione della funzione outer()
. Una volta che la funzione outer
ha terminato l’esecuzione, la variabile b
non esiste più.
Quando la funzione viene eseguita la seconda volta, le variabili della funzione vengono create di nuovo, e vivono solo fino a quando la funzione termina l’esecuzione.
Quindi, quando outer()
viene invocato la seconda volta:
- viene creata una nuova variabile
b
, il suo ambito è limitato alla funzioneouter()
, e il suo valore viene impostato a10
. - La linea successiva è una dichiarazione di funzione, quindi niente da eseguire.
-
return inner
restituisce l’intero corpo della funzioneinner
. - Il contenuto restituito dalla dichiarazione di ritorno è memorizzato in
Y
. - La funzione
outer()
termina l’esecuzione, e tutte le variabili nello scope diouter()
ora non esistono più.
Il punto importante qui è che quando la funzione outer()
viene invocata per la seconda volta, la variabile b
viene creata nuovamente. Inoltre, quando la funzione outer()
finisce di essere eseguita la seconda volta, questa nuova variabile b
cessa di esistere.
Questo è il punto più importante da realizzare. Le variabili all’interno delle funzioni esistono solo quando la funzione è in esecuzione, e cessano di esistere una volta che la funzione completa l’esecuzione.
Ora, torniamo al nostro esempio di codice e guardiamo X
e Y
. Poiché la funzione outer()
in esecuzione restituisce una funzione, le variabili X
e Y
sono funzioni.
Questo può essere facilmente verificato aggiungendo quanto segue al codice JavaScript:
console.log(typeof(X)); //X is of type function
console.log(typeof(Y)); //Y is of type function
Siccome le variabili X
e Y
sono funzioni, possiamo eseguirle. In JavaScript, una funzione può essere eseguita aggiungendo ()
dopo il nome della funzione, come X()
e Y()
.
Quando eseguiamo X()
e Y()
, stiamo essenzialmente eseguendo la funzione inner
.
Esaminiamo passo dopo passo cosa succede quando X()
viene eseguito per la prima volta:
- Variabile
a
viene creata, e il suo valore viene impostato a20
. - JavaScript ora cerca di eseguire
a + b
. Qui è dove le cose si fanno interessanti. JavaScript sa chea
esiste poiché lo ha appena creato. Tuttavia, la variabileb
non esiste più. Poichéb
fa parte della funzione esterna,b
esisterebbe solo mentre la funzioneouter()
è in esecuzione. Poiché la funzioneouter()
ha terminato l’esecuzione molto prima che noi invocassimoX()
, tutte le variabili nell’ambito della funzioneouter
cessano di esistere, e quindi la variabileb
non esiste più.
Come gestisce JavaScript questo?
Chiusure
La funzione inner
può accedere alle variabili della funzione racchiusa grazie alle chiusure in JavaScript. In altre parole, la funzione inner
conserva la catena di scope della funzione di chiusura nel momento in cui la funzione di chiusura è stata eseguita, e quindi può accedere alle variabili della funzione di chiusura.
Nel nostro esempio, la funzione inner
aveva conservato il valore di b=10
quando la funzione outer()
è stata eseguita, e continua a conservarlo (chiusura).
Ora si riferisce alla sua catena di scope e nota che ha il valore della variabile b
all’interno della sua catena di scope, poiché aveva racchiuso il valore di b
all’interno di una chiusura nel punto in cui la funzione outer
era stata eseguita.
Quindi, JavaScript conosce a=20
e b=10
, e può calcolare a+b
.
Puoi verificarlo aggiungendo la seguente linea di codice all’esempio sopra:
Apri l’elemento Inspect in Google Chrome e vai alla Console. Puoi espandere l’elemento per vedere effettivamente l’elemento Closure
(mostrato nella terzultima riga sotto). Notate che il valore di b=10
è conservato nel Closure
anche dopo che la funzione outer()
completa la sua esecuzione.
Ripercorriamo ora la definizione di chiusura che abbiamo visto all’inizio e vediamo se ora ha più senso.
Quindi la funzione interna ha tre catene di scope:
- accesso al proprio scope – variabile
a
- accesso alle variabili della funzione
outer
– variabileb
, che ha racchiuso - accesso a qualsiasi variabile globale che può essere definita
Chiusure in azione
Per portare a casa il punto delle chiusure, aumentiamo l’esempio aggiungendo tre righe di codice:
Quando si esegue questo codice, si vedrà il seguente output nel console.log
:
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
Esaminiamo questo codice passo dopo passo per vedere cosa succede esattamente e per vedere le chiusure in azione!
var X = outer(); // outer() invoked the first time
La funzione outer()
viene invocata la prima volta. I seguenti passi hanno luogo:
- Variabile
b
viene creata, ed è impostata su10
Variabilec
viene creata, e impostata su100
Chiamiamo questob(first_time)
ec(first_time)
per nostro riferimento. - La funzione
inner
viene restituita e assegnata aX
A questo punto, la variabileb
è racchiusa nella catena di scope della funzioneinner
come chiusura conb=10
, poichéinner
utilizza la variabileb
. - La funzione
outer
completa l’esecuzione e tutte le sue variabili cessano di esistere. La variabilec
non esiste più, anche se la variabileb
esiste come chiusura dentroinner
.
var Y= outer(); // outer() invoked the second time
- La variabile
b
viene creata nuovamente ed è impostata su10
La variabilec
viene creata nuovamente.
Nota che anche seouter()
è stato eseguito una volta prima che le variabilib
ec
cessassero di esistere, una volta che la funzione ha completato l’esecuzione vengono create come variabili nuove.
Chiamiamo questeb(second_time)
ec(second_time)
per nostro riferimento. - La funzione
inner
viene restituita e assegnata aY
A questo punto la variabileb
è racchiusa nella catena di scope della funzioneinner
come chiusura conb(second_time)=10
, poichéinner
usa la variabileb
. - La funzione
outer
completa l’esecuzione, e tutte le sue variabili cessano di esistere.
La variabilec(second_time)
non esiste più, anche se la variabileb(second_time)
esiste come chiusura dentroinner
.
Ora vediamo cosa succede quando vengono eseguite le seguenti linee di codice:
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third timeY(); // Y() invoked the first time
Quando X()
viene invocato per la prima volta,
- viene creata la variabile
a
, e impostata su20
- il valore di
a=20
, il valore dib
è quello della chiusura.b(first_time)
, quindib=10
- le variabili
a
eb
sono incrementate da1
-
X()
completa l’esecuzione e tutte le sue variabili interne – variabilea
– cessano di esistere.
Tuttavia,b(first_time)
è stato conservato come chiusura, quindib(first_time)
continua ad esistere.
Quando X()
viene invocato la seconda volta,
- la variabile
a
viene creata nuovamente, e impostata su20
Qualsiasi valore precedente della variabilea
non esiste più, poiché ha cessato di esistere quandoX()
ha completato l’esecuzione la prima volta. - il valore di
a=20
il valore dib
è preso dal valore di chiusura –b(first_time)
Nota anche che abbiamo incrementato il valore dib
di1
dalla precedente esecuzione, quindib=11
- le variabili
a
eb
sono aumentate di1
di nuovo -
X()
completa l’esecuzione e tutte le sue variabili interne – variabile a – cessano di esistere
Tuttavia,b(first_time)
è conservato in quanto la chiusura continua ad esistere.
Quando X()
viene invocato la terza volta,
- la variabile
a
viene creata nuovamente, e impostata su20
Qualsiasi valore precedente della variabilea
non esiste più, poiché ha cessato di esistere quandoX()
ha completato l’esecuzione la prima volta. - il valore di
a=20
, il valore dib
è dal valore di chiusura –b(first_time)
Nota anche che avevamo incrementato il valore dib
di1
nella precedente esecuzione, quindib=12
- le variabili
a
eb
sono incrementate da1
di nuovo -
X()
completa l’esecuzione, e tutte le sue variabili interne – variabilea
– cessano di esistere
Tuttavia,b(first_time)
viene conservato in quanto la chiusura continua ad esistere
Quando Y() viene invocato per la prima volta,
- la variabile
a
viene creata nuovamente e impostata su20
- il valore di
a=20
, il valore dib
è dal valore di chiusura –b(second_time)
, quindib=10
- le variabili
a
eb
sono incrementate di1
-
Y()
completa l’esecuzione, e tutte le sue variabili interne – variabilea
– cessano di esistere
Tuttavia,b(second_time)
è stato conservato come chiusura, quindib(second_time)
continua ad esistere.
Riservazioni conclusive
Le chiusure sono uno di quei concetti sottili in JavaScript che sono difficili da afferrare all’inizio. Ma una volta che le hai capite, ti rendi conto che le cose non avrebbero potuto andare diversamente.
Spero che queste spiegazioni passo dopo passo ti abbiano aiutato a capire davvero il concetto di chiusura in JavaScript!
Altri articoli:
- Una guida rapida alle funzioni “autoinvocanti” in Javascript
- Comprendere lo scope di funzione vs. lo scope di blocco in Javascript
- Come usare le Promesse in JavaScript
- Come costruire una semplice animazione Sprite in JavaScript