Che Cosa Sono Le Closures in JavaScript? E Come Usarle?

Scopri cosa sono le closures in JavaScript e come usarle nel tuo codice.

Che Cosa Sono Le Closures in JavaScript? E Come Usarle?

Questo post è un riadattamento tratto da Il Piccolo Libro di JavaScript, tutto quello che avresti voluto sapere sul linguaggio JavaScript (ma non hai mai osato chiedere).

In JavaScript non dobbiamo preoccuparci di gestire la memoria, a differenza dei linguaggi di programmazione low-level come C. Una delle caratteristiche di JavaScript è infatti quella di essere un linguaggio garbage collected.

In altre parole il motore svuota la memoria allocata per le variabili (globali e locali) non appena le funzioni terminano il loro lavoro. Per chiarire meglio considera questo esempio. Supponiamo di avere la necessità di un banale contatore, che viene incrementato ad ogni call di una funzione. Il primo approccio potrebbe essere questo:

var counter = 0;

function increment() {
  counter++;
  console.log(counter);
}

increment();
increment();

Il codice funziona e ritorna:

1
2

Ma abbiamo un problema evidente: counter è una variabile globale e le variabili globali sono qualcosa che vogliamo evitare ad ogni costo nel nostro codice.

Che cosa succede se un’altra funzione che non è increment inizia a modificare counter? Potremmo ritrovarci con un programma inaffidabile ed altamente inefficiente. Ci sono soluzioni?

Che cosa sono le closures in JavaScript? Alla ricerca di privacy

Un possibile espediente potrebbe essere quello di spostare counter all’interno di increment in modo da creare privacy per la nostra variabile:

function increment() {
  var counter = 0;
  counter++;
  console.log(counter);
}

increment();
increment();

Che cosa succede facendo girare questo codice? Ricorda: il motore JavaScript pulisce la memoria locale della funzione increment al termine di ogni esecuzione. Questo significa che il codice sopra produrrà:

1
1

Quando dichiaro e assegno counter come variabile locale il motore la “resetta” ad ogni esecuzione di increment.

Allo stesso tempo abbiamo visto sopra che usare variabili globali non è ottimale (mai contaminare il global scope!). Quindi? Che fare? E se potessimo “mantenere” la variabile counter tra un’esecuzione e l’altra?

L’idea è quella di avere uno stato che persiste e rimane agganciato alla nostra funzione. Si può fare una cosa del genere in JavaScript? La via d’uscita è lì che ci aspetta: è già nel linguaggio JavaScript, e non dobbiamo fare altro che sfruttarla.

Che cosa sono le closures in JavaScript? M’illumino di closure

La soluzione che stiamo cercando sta in uno dei tratti più oscuri di JavaScript, uno degli argomenti che confonde anche gli sviluppatori più esperti.

Hai utilizzato mille volte questa caratteristica di JavaScript, magari senza saperlo. Per “mantenere” infatti la variabile counter tra un’esecuzione e l’altra, come uno stato persistente, non dobbiamo fare altro che ritornare una funzione dalla nostra funzione.

Sto parlando delle closures! La traduzione esatta di closures sarebbe “chiusure” per il modo in cui le funzioni “chiudono” sopra le variabili che stanno attorno. Ma la definizione che preferisco è: conservare in modo persistente le variabili che circondano una funzione, tra un’esecuzione e l’altra.

Caratteristica fondamentale affinchè si verifichi la closure è inglobare una funzione all’interno di una funzione contenitore, e ritornare poi la funzione interna. Per sfruttare le closures infatti dobbiamo riscrivere il nostro codice in modo leggermente diverso:

function increment() {
  var counter = 0;
  return function addToCounter() {
    counter++;
    console.log(counter);
  };
}

La novità rispetto alla prima versione è la funzione interna addToCounter. Tutto l’ambiente di variabili contiguo a questa funzione, che viene ritornata dalla funzione “contenitore” increment viene preservato, anche tra un’esecuzione e l’altra.

Il motore infatti “aggancia” la variabile counter alla funzione addToCounter, anche quando quest’ultima viene chiamata in un punto diverso del codice. Vantaggi immediati delle closures: il global scope rimane pulito ed abbiamo finalmente privacy per le nostre variabili. Resta un’ultima cosa da fare per testare il nostro codice. Primo, “catturiamo” addToCounter all’interno di una variabile:

var result = increment();

Ti stai chiedendo perchè eseguo increment e lo assegno a result quando ho parlato un secondo fa di catturare addToCounter?

increment è solo un contenitore che ritorna qualcosa. Ci serve per dare privacy alla funzione addToCounter e per contenere la variabile counter. A questo punto non resta altro che eseguire result. Questa variabile infatti è diventata una referenza per la nostra cara addToCounter, che ora si porta dietro l’ambiente delle variabili al momento dell’esecuzione. Il test finale:

result();
result();

produrrà:

1
2

Il nostro codice funziona!

Che cosa sono le closures in JavaScript? Come usare le closure nel mondo reale

Le closures potrebbero sembra qualcosa di arcano ma in parole povere è sufficiente ritornare una funzione interna da una funzione contenitore.

In questo modo tutto l’ambiente di variabili attiguo alla funzione interna persiste attraverso l’esecuzione. Se non facessimo affidamento sulle closure il motore JavaScript pulirebbe ogni volta le nostre variabili, cancellandole dalla memoria.

Ma a parte questa banale applicazione quali sono gli utilizzi pratici delle closures? Le closures sono alla base del module pattern.

Il module pattern è un tentativo di organizzare il codice JavaScript in unità indipendenti, mimando le classi presenti in altri linguaggi di programmazione. Se volessimo fare refactoring del nostro contatore per modularizzare il codice avremmo qualcosa del genere:

var incrementModule = (function increment() {
  var counter = 0;
  function addToCounter() {
    counter++;
    console.log(counter);
  }

  return { addToCounter };
})();

incrementModule.addToCounter();
incrementModule.addToCounter();
incrementModule.addToCounter();

Non disperare se questa sintassi sembra non avere senso. I moduli in JavaScript infatti sono funzioni IIFE (immediately invoked function expression) che ritornano un oggetto.

Puoi approfondire le IIFEE sulla documentazione MDN. Se vuoi saperne di più sul Module Pattern ti suggerisco la lettura di Mastering the Module Pattern di Todd Motto.

Che cosa sono le closures in JavaScript? In conclusione

In JavaScript non esiste il concetto di variabili private. Ci sono solo variabili globali e variabili locali che vengono comunque pulite dopo che ogni funzione viene eseguita.

Questo rende all’apparenza impossibile mantenerle intatte tra un’esecuzione e l’altra. Ma sfruttando le closures, una peculiare caratteristica di JavaScript, possiamo:

  • dare privacy alle variabili
  • mantenere il valore delle variabili locali tra un’esecuzione e l’altra

La closure viene attivata quando ritorniamo una funzione da un’altra funzione. Le closures sono molto importanti in JavaScript perchè consentono di “nascondere” le variabili globali che in alcune situazioni possono addirittura essere create accidentalmente.

Le closures sono anche alla base del Module Pattern, un design pattern molto comune in JavaScript.

Grazie per aver letto, alla prossima!

Il Piccolo Libro di JavaScript, tutto quello che avresti voluto sapere sul linguaggio JavaScript (ma non hai mai osato chiedere).

Il Piccolo Libro di JavaScript, tutto quello che avresti voluto sapere sul linguaggio JavaScript (ma non hai mai osato chiedere).

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.