Le Closure in JavaScript: Un concetto fondamentale spiegato facilmente

  



Se hai già programmato in JavaScript, probabilmente ti sei imbattuto nel termine "closure". 

È uno di quei concetti che, a prima vista, può sembrare un po' enigmatico, persino per programmatori esperti. Ma non temere! 

In questo articolo, ti guideremo passo dopo passo alla scoperta delle closure, partendo dalle basi per arrivare a capire cosa sono realmente e perché sono così potenti.

🔗 Ti piace Techelopment? Dai un'occhiata al sito per tutti i dettagli!

Passo 1: Le Funzioni in JavaScript - Un Concetto Chiave

Prima di addentrarci nelle closure, è fondamentale rinfrescare un concetto basilare di JavaScript: le funzioni. In JavaScript, le funzioni sono "cittadini di prima classe". Cosa significa? Significa che possono essere trattate come qualsiasi altro valore:

  • Possono essere assegnate a variabili.
  • Possono essere passate come argomenti ad altre funzioni.
  • Possono essere restituite da altre funzioni.

Questo ultimo punto, in particolare, è cruciale per comprendere le closure.

Esempio:

function saluta(nome) {
  return "Ciao, " + nome + "!";
}

let miaFunzione = saluta; // Assegno la funzione a una variabile
console.log(miaFunzione("Marco")); // Output: Ciao, Marco!

Passo 2: Lo Scope e il "Contesto" delle Variabili

Ogni funzione in JavaScript crea il proprio "scope". Lo scope è fondamentalmente la "zona" all'interno della quale le variabili definite in quella funzione sono accessibili. Quando una funzione viene eseguita, crea un contesto di esecuzione (o execution context) che include il suo scope.

Consideriamo questo esempio:

function creaContatore() {
  let contatore = 0; // Questa variabile vive solo all'interno di creaContatore

  function incrementa() {
    contatore++;
    console.log(contatore);
  }

  incrementa();
  incrementa();
}

creaContatore(); // Output: 1, 2
// console.log(contatore); // Errore: contatore non è definito qui!

Nell'esempio sopra, la variabile contatore è accessibile solo all'interno della funzione creaContatore e delle funzioni definite al suo interno (come incrementa). Una volta che creaContatore ha finito di essere eseguita, normalmente ci aspetteremmo che contatore venga "dimenticato" dalla memoria. Ma è qui che entra in gioco la magia!


Passo 3: La Nascita della Closure - Funzioni Nidificate e Persistenza dello Scope

Immagina ora di voler restituire la funzione incrementa dalla funzione creaContatore.

function creaContatoreMagico() {
  let contatore = 0; // Questa variabile dovrebbe "morire" alla fine di creaContatoreMagico

  function incrementa() {
    contatore++;
    return contatore;
  }

  return incrementa; // Restituisco la funzione incrementa
}

let ilMioContatore = creaContatoreMagico(); // Ora ilMioContatore è la funzione incrementa

console.log(ilMioContatore()); // Output: 1
console.log(ilMioContatore()); // Output: 2
console.log(ilMioContatore()); // Output: 3

Ecco la sorpresa! Nonostante la funzione creaContatoreMagico abbia terminato la sua esecuzione, la variabile contatore non è stata eliminata dalla memoria! Ogni volta che chiamiamo ilMioContatore(), la variabile contatore viene incrementata e mantiene il suo stato precedente.

Questo fenomeno è ciò che chiamiamo closure.


Cos'è una Closure? La Definizione Semplice ma Efficace:

Una closure si verifica quando una funzione "ricorda" e può accedere al suo scope lessicale (ovvero, le variabili del suo ambiente di creazione) anche quando viene eseguita al di fuori di quell'ambiente.

In termini ancora più semplici:

Una closure è una funzione che, insieme al suo ambiente lessicale circostante (le variabili che erano nel suo scope quando è stata creata), forma un'unità. La funzione "cattura" queste variabili e le mantiene in vita, anche dopo che la funzione esterna che le ha definite ha terminato l'esecuzione.

Nel nostro esempio creaContatoreMagico, la funzione incrementa forma una closure con la variabile contatore. Nonostante creaContatoreMagico sia terminata, incrementa "si ricorda" di contatore e può ancora leggerla e modificarla.


Perché le Closure sono Utili? Applicazioni Pratiche

Le closure sono un concetto potente e trovano molte applicazioni pratiche in JavaScript:

  1. Mantenere lo Stato Privato: Come visto nell'esempio del contatore, le closure ci permettono di creare variabili "private" che non possono essere accedute direttamente dall'esterno, garantendo l'incapsulamento dei dati. Questo è fondamentale per creare moduli e oggetti con stato.
  2. Creazione di Funzioni Fabbrica (Factory Functions): Possiamo usare le closure per creare funzioni che, a loro volta, generano altre funzioni con un comportamento personalizzato.
    function creaSalutoPersonalizzato(salutoIniziale) {
      return function(nome) {
        return salutoIniziale + ", " + nome + "!";
      };
    }
    
    let salutaInItaliano = creaSalutoPersonalizzato("Ciao");
    let salutaInInglese = creaSalutoPersonalizzato("Hello");
    
    console.log(salutaInItaliano("Giulia")); // Output: Ciao, Giulia!
    console.log(salutaInInglese("John"));   // Output: Hello, John!
    
  3. Gestione di Eventi e Callback: Nelle applicazioni web, spesso si usano closure per accedere a variabili del contesto circostante all'interno di funzioni di callback per eventi.
    function gestisciClick(idElemento) {
      document.getElementById(idElemento).addEventListener('click', function() {
        console.log("Hai cliccato sull'elemento con ID: " + idElemento);
      });
    }
    
    // Qui la closure cattura 'idElemento' per usarlo nella funzione di callback
    gestisciClick('bottoneSpeciale');
    
  4. Currying e Programmazione Funzionale: Le closure sono un pilastro della programmazione funzionale, permettendo tecniche come il currying (trasformare una funzione con argomenti multipli in una sequenza di funzioni con un singolo argomento).

In Conclusione

Le closure non sono un costrutto magico o esoterico, ma una conseguenza naturale del modo in cui JavaScript gestisce lo scope e le funzioni. Comprendere le closure ti aprirà le porte a scrivere codice più modulare, flessibile e potente.

Ricorda: la chiave è che una funzione "porta con sé" l'ambiente in cui è stata creata. Questo le permette di accedere e interagire con le variabili di quell'ambiente, anche quando la funzione che le ha generate non è più in esecuzione.

Quindi, la prossima volta che sentirai parlare di closure, non farti spaventare. Pensa semplicemente a una funzione che ha una memoria speciale!



Follow me #techelopment

Official site: www.techelopment.it
facebook: Techelopment
instagram: @techelopment
X: techelopment
Bluesky: @techelopment
telegram: @techelopment_channel
whatsapp: Techelopment
youtube: @techelopment