Che cosa sono le closures in JavaScript? E come usarle?

Questo post è estratto da Il Piccolo Libro di JavaScript, tutto quello che avresti voluto sapere su JavaScript ma non hai mai osato chiedere!

M’illumino di closure: scopri finalmente cosa sono le closures in JavaScript, senza passare prima dallo psicologo!

Hai letto Scope & Closures di Kyle Simpson ma conosci le closures meno di prima? Sei nel posto giusto!

M'illumino di closure: scopri finalmente cosa sono le closures in JavaScript, senza passare prima dallo psicologo!

Che cosa sono le closures in JavaScript? Garbage collected!

A differenza dei linguaggi di programmazione low-level come C, in JavaScript non dobbiamo preoccuparci di gestire la memoria (per fortuna).

Una delle caratteristiche di JavaScript è infatti quella di essere un linguaggio garbage collected.

In altre parole il motore JavaScript svuota la memoria allocata per le variabili (globali e locali) non appena le funzioni terminano i loro compiti.

A breve vedremo gli effetti di questo comportamento in azione ma per il momento cominciamo dalle variabili globali.

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:

Il codice funziona e ritorna:

Ma abbiamo un problema evidente: le variabili globali sono qualcosa che vogliamo evitare ad ogni costo nel nostro codice.

E counter al momento è una variabile globale.

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? Anche le variabili hanno bisogno di privacy!

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

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à:

Provare per credere.

Il nostro contatore non funziona!

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 (dobbiamo sempre evitare l’inquinamento del global scope).

Quindi?

Che fare?

E se potessimo “mantenere” la variabile counter tra un’esecuzione e l’altra?

L’idea è quella di avere dei dati, 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 stavamo cercando sta in uno dei tratti più oscuri di JavaScript, uno degli argomenti che spaventa 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.

In altre parole stiamo parlando di 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:

La novità rispetto alla prima versione è la funzione interna addToCounter.

Tutto l‘ambiente di variabili contiguo ad addCounter viene preservato, anche tra un’esecuzione e l’altra.

Il “trucco” sta nel ritornare addToCounter dalla funzione increment (che ne diventa il contenitore).

Il motore infatti “aggancia” la variabile counter alla funzione addToCounter, anche quando quest’ultima viene chiamata in un punto diverso del codice.

Vantaggi subito visibili delle closures: l’ambiente delle variabili globale rimane pulito, privacy per le variabili.

Resta un’ultima cosa da fare per testare il nostro codice. Primo, “catturiamo” addToCounter all’interno di una variabile:

che può essere tradotto come: var result che contiene la variabile counter e la funzione addToCounter.

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:

produrrà:

Il nostro contatore funziona!

Come vedi sfruttare la potenza delle closures non richiede anni di esperienza con il linguaggio.

Basta ritornare una funzione da una funzione per vedere le closures in azione (fa anche rima)!

Che cosa sono le closures in JavaScript? Closures e Module pattern

Ma a parte questa banale applicazione sul contatore, quali sono gli utilizzi pratici delle closures?

Le closures sono alla base del Module pattern in JavaScript.

In JavaScript infatti non esistono classi nel vero senso del termine.

Puoi fare affidamento su prototype per agganciare metodi ad una serie di oggetti.

Ma esiste una soluzione più elegante: i moduli.

Come ciliegina sulla torta il nostro codice può essere riscritto sotto forma di “modulo”. Ovvero una IIFE (immediately invoked function) che ritorna un oggetto con dei metodi associati:

Per approfondire il Module pattern ti consiglio la lettura di Learning JavaScript Design Patterns di Addy Osmani.

Un altro articolo fondamentale per approfondire è Mastering the Module pattern di Todd Motto.

Che cosa sono le closures in JavaScript? In conclusione

Le closures ti sembravano qualcosa di così complicato.

E invece: si tratta di 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.

La traduzione esatta di closures in italiano è “chiusure” per il modo in cui le funzioni hanno visibilità sulle variabili nelle immediate vicinanze.

Ma la definizione che preferisco di closure è: conservare in modo persistente le variabili che circondano una funzione, tra un’esecuzione e l’altra.

Guarda un estratto video della lezione sulle closures (parte del programma di formazione JavaScript):

Protip: JavaScript non è l’unico linguaggio che fa uso delle closures!

Grazie per aver letto! Alla prossima!

Questo post è estratto da Il Piccolo Libro di JavaScript, tutto quello che avresti voluto sapere su JavaScript ma non hai mai osato chiedere!