Closures
By Sergio Spina
- 3 minutes read - 584 wordsLe closures sono una caratteristica importantissima, addirittura fondamentale nel moderno JavaScript; il concetto di closures attiene esclusivamente alle funzioni (non ci sono closures senza funzioni), e descrive il fenomeno per il quale alcune variabili, dichiarate all’interno di funzioni, sono accessibili anche all’esterno della funzione nella quale sono dichiarate. Questo concetto sembra essere controintuitivo: in fondo una variabile è visibile solo all’interno dello scope nel quale è dichiarata, e una funzione apre e chiude un proprio scope. Dunque, come può essere possibile che una variabile dichiarata all’interno di una funzione sia accessibile anche all’esterno della funzione stessa?
Una funzione può restituire una funzione
Vediamo un esempio, utile anche per osservare in azione il meccanismo delle closures.
1function getAnimal(animalId) {
2 let animals = {
3 1: 'wolf',
4 2: 'horse',
5 3: 'chicken',
6 4: 'dog',
7 5: 'octopus',
8 };
9
10 return function setVerse(verse) {
11 return `${animals[animalId]} says '${verse}'`;
12 };
13}
14
15let whatAnimalSays = getAnimal(5);
16
17console.log(whatAnimalSays('bloubl')); // octopus says 'bloubl'
18
19whatAnimalSays = getAnimal(3);
20
21console.log(whatAnimalSays('yeeak')); // chicken says 'yeeak'
Vediamo cosa succede: la funzione getAnimal
prima dichiara la variabile animals
, un oggetto
contenente cinque nomi di animali indicizzati da 1 a 5, poi crea e restituisce la funzione
setVerse
che accede alla variabile animals
e ne legge un valore per costruire una stringa.
Il programma prosegue dichiarando la variabile whatAnimalSays
che viene inizializzata alla
funzione restituita da getAnimal
. whatAnimalSays
viene infine passata a console.log
per
verificarne l’output.
Come si può notare, i nomi di animale restituiti dalla funzione setVerse
sono sempre diversi e
dipendono dal parametro passato a getAnimal
; setVerse
“ricorda” la variabile animals
anche se
la funzione getAnimal
è ormai chiusa così come il suo scope. In altre parole la funzione
setVerse
racchiude con se (closure) la variabile animals
e accede ad essa anche se la
funzione getAnimal
(creatrice sia di animals
che di setVerse
) non esercita più il suo
controllo di scope.
Vediamo un altro esempio che mostra il fenomeno dell’accesso ad una variabile dichiarata all’interno di una funzione chiusa.
1function createColorRandomizer() {
2 let colors = ['white', 'red', 'green', 'gray', 'blue', 'yellow', 'black', 'brown'];
3
4 return function chooseColor() {
5 let index = Math.floor(Math.random() * colors.length);
6 return colors[index];
7 };
8}
9
10let sprayer = createColorRandomizer();
11
12console.log(sprayer()); // black
13console.log(sprayer()); // blue
14console.log(sprayer()); // green
Come prima, sprayer
è una istanza di chooseColor
; quest’ultima “racchiude in se” (closure) la
variabile colors
e vi accede in lettura anche dal di fuori della funzione createColorRandomizer
.
Una closure può anche scrivere in una variabile
Finora abbiamo visto che che la closure può accedere alle variabili in lettura; ma nulla impedisce che possa accedervi anche in scrittura. L’esempio che segue è piuttosto famoso.
1function createCounter() {
2 let counter = 0;
3
4 return function incrementer() {
5 counter += 1;
6 return counter;
7 };
8}
9
10let tickA = createCounter();
11let tickB = createCounter();
12
13console.log(tickA()); // 1
14console.log(tickA()); // 2
15console.log(tickA()); // 3
16console.log(tickA()); // 4
17console.log(tickB()); // 1
18console.log(tickB()); // 2
19console.log(tickB()); // 3
Qui tickA
e tickB
sono due istanze di incrementer
; questa è una funzione che accede alla
variabile counter
, la incrementa di uno e ne restituisce il valore. Si noti come ogni volta che
viene chiamata la funzione createCounter
venga creata una nuova istanza di counter
: tickA
e
tickB
non accedono allo stesso counter
ma ognuna ad una variabile contatore diversa, come si può
verificare dall’output mandato dalle funzioni alla console.
Nel banner: Guido Van Rossum, creatore del linguaggio Python.