Full Stack

Cosa sono le closure in JavaScript

Autore

Manuel Ricci

Abbiamo visto lo scope, abbiamo visto le funzioni è giunto il momento di approfondire un concetto che si lega ad entrambi e che rappresenta una delle più potenti caratteristiche di JavaScript. In questo articolo parleremo delle tanto anticipate closure.

Di cosa stiamo parlando?

Come già visto nell'articolo relativo alle funzioni, JavaScript permette di annidarle, garantendo alla funzione interna (inner function) l'accesso completo alle variabili e alle funzioni definite nella funzione esterna (outer function).

Questo accesso è possibile solo dalla inner function verso la outer function e non viceversa. La funzione esterna non ha accesso a ciò che è stato definito all'interno della inner function.

1function outerFunction() {
2  const costante1 = "un valore"
3  // Qui ho accesso solo allo scope dell'outerFunction.
4  function innerFunction () {
5    // Qui ho accesso allo scope della innerFunction e a quello di outerFunction.
6  }
7}

Quando viene creata una closure

Una closure viene creata quando viene definita una funzione. Una volta che la funzione viene eseguita, le closure al loro interno potranno accedere ai dati nel loro scope.

Un'interessante analogia per comprendere meglio le closure è quella che Samer Buna ha scritto nel suo articolo delle closure su FreeCodeCamp.

Continua dopo il banner

E così vuoi diventare un programmatore, il mio ebook dove do qualche consiglio ha chi si vuole lanciare nella carriera della programmazione. Diciamo che ho raccolto i consigli che avrei voluto sentirmi dire quando ho iniziato.

E così vuoi diventare un programmatore, il mio ebook dove do qualche consiglio ha chi si vuole lanciare nella carriera della programmazione. Diciamo che ho raccolto i consigli che avrei voluto sentirmi dire quando ho iniziato.

Scopri cosa contiene

Provate a pensare ad un auto come se fosse una funzione, di base viene fornita con alcune funzioni come accensione, accellerazione e decellerazione. Queste funzioni vengono eseguite dal guidatore ogni volta che usa l'auto.

Proseguendo con questa analogia proviamo ad approfondire la funzione accellerazione:

1function accellerazione(forza) {
2  // La macchina è accesa?
3  // C'è benzina?
4  // La marcia è inserita?
5  // Altri check...
6  // Se tutto ok, bruciamo carburante in base alla forza applicata al pedale di accellerazione
7}

Come si può osservare questa funzione dipende da parecchie variabili, gestite da altre funzioni della macchina e quindi disponibili al di fuori del suo stesso scope.

Quando verrà eseguita la closure permetterà alla funzione accellerazione di accedere a tutta una serie di variabili e funzioni non disponibili nel suo scope, ma in quello esterno che gli permetteranno di assolvere al suo compito e produrre il suo output (l'accellerazione del veicolo, se tutto è ok ovviamente).

Da sottolineare comunque è il concetto che le closure non danno un valore fisso alla funzione di accellerazione, ma danno il permesso per accedere a quei valori nel momento in cui la funzione accellerazione viene eseguita.

Scope e closure sono due cose differenti

Come già visto nel video dedicato, le funzioni hanno un proprio scope inaccessibile dall'esterno della funzione stessa. Lo scope viene creato quando una funzione viene chiamata, mentre la closure viene creata durante la definizione di una funzione. In questo modo quando una funzione viene chiamata, la closure già esistente garantirà l'accesso agli scope disponibili nelle altre funzioni annidate in base al livello di annidamento.

1function outerFunction() {
2  // Posso accedere solo allo scope di outerFunction.
3  function innerFunction() {
4    // Posso accedere allo scope di innerFunction e di outerFunction
5    function innerInnerFunction() {
6      // Posso accedere allo scope di outerFunction, innerFunction e innerInnerFunction.
7    }
8  }
9}

Il ciclo di vita di scope e closure è differente, perché dal momento che la funzione termina la sua serie di istruzioni il suo scope viene distrutto, mentre invece la closure è permanente.

Qualche esempio per rafforzare il concetto

Prendiamo come esempio il seguente codice:

1const a = 1;
2void function paperino() {
3  // scope: paperino
4  // closure: paperino, globale
5  const b = 2;
6
7  void function topolino() {
8    // scope: topolino
9    // closure: topolino, paperino, globale
10    const c = 3;
11
12    void function pippo() {
13      // scope: pippo
14      // closure: pippo, topolino, paperino, globale
15      const d = 4;
16      console.log(a + b + c + d); // Output: 10
17    }();
18  }();
19}();

Possiamo vedere come le tre funzioni definite vengono immediatamente eseguite (void function nomeFunzione() {...}()) grazie all'operatore void.

  • La closure di paperino() fornisce l'accesso sia allo scope di paperino che a quello globale
  • La closure di topolino() fornisce accesso al suo scope, a quello di paperino() e a quello globale.
  • La closure di pippo() fornisce accesso a tutti gli scope precedenti più il suo.

La relazione però non è sempre così lineare. Vi pare? Stiamo parlando di programmazione, non ci sono mai gioie :)

Prendiamo come esempio questo nuovo snippet:

1let v = 1;
2const funzione1 = function () {
3  console.log(v);
4}
5const funzione2 = function () {
6  let v = 2;
7  funzione1(); // L'output sarà 1 o 2?
8}
9funzione2();

Qui abbiamo definito e chiamato una funzione in scope differenti e quindi sorge la domanda. Cosa verrà mostrato? 1 o 2? Secondo te?

Spazio per riflessione

Se avete compreso quello che avete letto finora starete pensando a 2, ma purtroppo la risposta è 1. La ragione è semplice, scope e closure sono due cose differenti. console.log usa la closure di funzione1(), la quale è creata durante la definizione della funzione stessa. Gli unici scope a cui ha accesso funzione1() sono quindi il suo scope e quello globale.

Le closure possono condividere lo scope

Tornando al nostro esempio della macchina fatto in precedenza, la funzione accellerazione dipendeva da diversi variabili disponibili in scope differenti. Oltre al fatto che le closure hanno la possibilità di leggere e anche di scrivere le variabili "ereditate" dagli altri scope, tali scope possono essere condivisi.

1function calcoli(a) {
2
3  function somma() {
4    a = a + a;
5    console.log(a);
6  }
7
8  function moltiplica() {
9    a = a * a;
10    console.log(a);
11  }
12
13  return {somma, moltiplica};
14
15}
16
17let {somma, moltiplica} = calcoli(10);
18somma(); // Output: 20;
19moltiplica(); // Output: 400;
20somma(); // Output: 800;

In questo ultimo snippet la funzione calcoli() si aspetta un parametro. Le funzioni al suo interno usano quel parametro per poter fare i loro calcoli. Le closure create per somma() e moltiplica() hanno accesso allo scope di calcoli() e siccome tutte e due modificano il parametro a, le ultime 3 righe di codice mostrano come lo scope in questo caso sia condiviso e che i calcoli non siano fini a se stessi.

Con questo spero che il concetto di closure sia più chiaro. Stay Safe!

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.