Una semplice guida per aiutarti a capire chiusure in JavaScript

Le chiusure in JavaScript sono uno di quei concetti che molti fanno fatica a trovare la testa. Nel seguente articolo, spiegherò in termini chiari cos'è una chiusura e guiderò il punto a casa usando semplici esempi di codice.

Cos'è una chiusura?

Una chiusura è una funzione di JavaScript in cui una funzione interna ha accesso alle variabili della funzione esterna (che racchiude) - una catena di ambito.

La chiusura ha tre catene di portata:

  • ha accesso al proprio ambito: variabili definite tra parentesi graffe
  • ha accesso alle variabili della funzione esterna
  • ha accesso alle variabili globali

A chi non lo sapesse, questa definizione potrebbe sembrare solo un sacco di gergo!

Ma cos'è veramente una chiusura?

Una chiusura semplice

Diamo un'occhiata a un semplice esempio di chiusura in JavaScript:

funzione esterna () {
   var b = 10;
   funzione inner () {
        
         var a = 20;
         console.log (a + b);
    }
   ritorno interiore;
}

Qui abbiamo due funzioni:

  • una funzione esterna che ha una variabile b, e restituisce la funzione interna
  • una funzione interna interna che ha la sua variabile chiamata a, e accede a una variabile esterna b, all'interno del suo corpo di funzione

L'ambito della variabile b è limitato alla funzione esterna e l'ambito della variabile a è limitato alla funzione interna.

Richiamiamo ora la funzione outer () e memorizziamo il risultato della funzione outer () in una variabile X. Quindi invochiamo una seconda volta la funzione outer () e la memorizziamo nella variabile Y.

funzione esterna () {
   var b = 10;
   funzione inner () {
        
         var a = 20;
         console.log (a + b);
    }
   ritorno interiore;
}
var X = esterno (); // outer () ha invocato la prima volta
var Y = esterno (); // outer () ha invocato la seconda volta

Vediamo passo passo cosa succede quando viene invocata per la prima volta la funzione outer ():

  1. Viene creata la variabile b, il suo ambito è limitato alla funzione outer () e il suo valore è impostato su 10.
  2. La riga successiva è una dichiarazione di funzione, quindi nulla da eseguire.
  3. Nell'ultima riga, return inner cerca una variabile chiamata inner, trova che questa variabile inner è effettivamente una funzione e quindi restituisce l'intero corpo della funzione inner.
    [Si noti che l'istruzione return non esegue la funzione interna - una funzione viene eseguita solo quando seguita da () -, ma piuttosto l'istruzione return restituisce l'intero corpo della funzione.]
  4. I contenuti restituiti dall'istruzione return sono archiviati in X.
    Pertanto, X memorizzerà quanto segue:
     funzione inner () {
     var a = 20;
    console.log (a + b);
    }
  5. La funzione outer () termina l'esecuzione e tutte le variabili nell'ambito di outer () ora non esistono più.

Quest'ultima parte è importante da capire. Quando una funzione completa l'esecuzione, tutte le variabili definite all'interno dell'ambito della funzione cessano di esistere.

La durata di una variabile definita all'interno di una funzione è la durata dell'esecuzione della funzione.

Ciò significa che in console.log (a + b), la variabile b esiste solo durante l'esecuzione della funzione outer (). Al termine dell'esecuzione della funzione esterna, la variabile b non esiste più.

Quando la funzione viene eseguita la seconda volta, le variabili della funzione vengono nuovamente create e rimangono attive solo fino al completamento dell'esecuzione della funzione.

Pertanto, quando outer () viene richiamato la seconda volta:

  1. Viene creata una nuova variabile b, il suo ambito è limitato alla funzione outer () e il suo valore è impostato su 10.
  2. La riga successiva è una dichiarazione di funzione, quindi nulla da eseguire.
  3. return inner restituisce l'intero corpo della funzione inner.
  4. I contenuti restituiti dall'istruzione return sono archiviati in Y.
  5. La funzione outer () termina l'esecuzione e tutte le variabili nell'ambito di outer () ora non esistono più.

Il punto importante qui è che quando la seconda funzione viene invocata, la variabile b viene nuovamente creata. Inoltre, quando la funzione outer () termina l'esecuzione la seconda volta, questa nuova variabile b cessa nuovamente di esistere.

Questo è il punto più importante da realizzare. Le variabili all'interno delle funzioni nascono solo quando la funzione è in esecuzione e cessano di esistere quando le funzioni completano l'esecuzione.

Ora, torniamo al nostro esempio di codice e osserviamo X e Y. Poiché la funzione outer () in esecuzione restituisce una funzione, le variabili X e Y sono funzioni.

Questo può essere facilmente verificato aggiungendo quanto segue al codice JavaScript:

console.log (typeof (X)); // X è di tipo funzione
console.log (typeof (Y)); // Y è di tipo funzione

Poiché le variabili X e Y sono funzioni, possiamo eseguirle. In JavaScript, una funzione può essere eseguita aggiungendo () dopo il nome della funzione, come X () e Y ().

funzione esterna () {
var b = 10;
   funzione inner () {
        
         var a = 20;
         console.log (a + b);
    }
   ritorno interiore;
}
var X = esterno ();
var Y = esterno ();
// fine delle esecuzioni della funzione outer ()
X(); // X () ha invocato la prima volta
X(); // X () invocato la seconda volta
X(); // X () invocato la terza volta
Y (); // Y () ha invocato la prima volta

Quando eseguiamo X () e Y (), stiamo essenzialmente eseguendo la funzione interna.

Esaminiamo passo per passo cosa succede quando X () viene eseguito la prima volta:

  1. La variabile a viene creata e il suo valore è impostato su 20.
  2. JavaScript ora tenta di eseguire a + b. Qui è dove le cose si fanno interessanti. JavaScript sa che esiste da quando lo ha appena creato. Tuttavia, la variabile b non esiste più. Poiché b fa parte della funzione esterna, b esisterebbe solo mentre la funzione outer () è in esecuzione. Poiché la funzione outer () ha terminato l'esecuzione molto prima che invocassimo X (), tutte le variabili nell'ambito della funzione esterna cessano di esistere, e quindi la variabile b non esiste più.

Come lo gestisce JavaScript?

chiusure

La funzione interna può accedere alle variabili della funzione che racchiude a causa di chiusure in JavaScript. In altre parole, la funzione interna conserva la catena di portata della funzione che racchiude nel momento in cui è stata eseguita la funzione di accluso, e quindi può accedere alle variabili della funzione di accluso.

Nel nostro esempio, la funzione interna aveva conservato il valore di b = 10 quando la funzione esterna () veniva eseguita e continuava a preservarla (chiudendola).

Fa ora riferimento alla sua catena di portata e nota che ha il valore della variabile b all'interno della sua catena di portata, poiché aveva racchiuso il valore di b all'interno di una chiusura nel punto in cui la funzione esterna era stata eseguita.

Pertanto, JavaScript conosce a = 20 e b = 10 e può calcolare a + b.

Puoi verificarlo aggiungendo la seguente riga di codice nell'esempio sopra:

funzione esterna () {
var b = 10;
   funzione inner () {
        
         var a = 20;
         console.log (a + b);
    }
   ritorno interiore;
}
var X = esterno ();
console.dir (X); // usa console.dir () invece di console.log ()

Apri l'elemento Inspect in Google Chrome e vai alla console. È possibile espandere l'elemento per visualizzare effettivamente l'elemento di chiusura (mostrato nella terza all'ultima riga di seguito). Si noti che il valore di b = 10 viene conservato nella chiusura anche dopo che la funzione outer () ha completato la sua esecuzione.

La variabile b = 10 è conservata nella Chiusura, Chiusure in Javascript

Rivisitiamo ora la definizione di chiusure che abbiamo visto all'inizio e vediamo se ora ha più senso.

Quindi la funzione interna ha tre catene di portata:

  • accesso al proprio ambito di applicazione - variabile a
  • accesso alle variabili della funzione esterna - variabile b, che racchiude
  • accesso a tutte le variabili globali che possono essere definite

Chiusure in azione

Per portare a casa il punto di chiusura, aumentiamo l'esempio aggiungendo tre righe di codice:

funzione esterna () {
var b = 10;
var c = 100;
   funzione inner () {
        
         var a = 20;
         console.log ("a =" + a + "b =" + b);
         a ++;
         b ++;
    }
   ritorno interiore;
}
var X = esterno (); // outer () ha invocato la prima volta
var Y = esterno (); // outer () ha invocato la seconda volta
// fine delle esecuzioni della funzione outer ()
X(); // X () ha invocato la prima volta
X(); // X () invocato la seconda volta
X(); // X () invocato la terza volta
Y (); // Y () ha invocato la prima volta

Quando esegui questo codice, vedrai il seguente output in console.log:

a = 20 b = 10
a = 20 b = 11
a = 20 b = 12
a = 20 b = 10

Esaminiamo questo codice passo dopo passo per vedere cosa sta succedendo esattamente e per vedere le chiusure in Azione!

var X = esterno (); // outer () ha invocato la prima volta

La funzione outer () viene invocata la prima volta. Si svolgono le seguenti fasi:

  1. La variabile b viene creata ed è impostata su 10
    La variabile c viene creata e impostata su 100
    Chiamiamo questo b (first_time) ec (first_time) per nostro riferimento.
  2. La funzione interna viene restituita e assegnata a X
    A questo punto, la variabile b è racchiusa nella catena dell'ambito della funzione interna come una chiusura con b = 10, poiché inner utilizza la variabile b.
  3. La funzione esterna completa l'esecuzione e tutte le sue variabili cessano di esistere. La variabile c non esiste più, anche se la variabile b esiste come chiusura all'interno.
var Y = esterno (); // outer () ha invocato la seconda volta
  1. La variabile b viene creata di nuovo ed è impostata su 10
    La variabile c viene creata di nuovo.
    Si noti che anche se outer () è stato eseguito una volta prima che le variabili b e c cessassero di esistere, una volta completata la funzione, vengono create come variabili nuove di zecca.
    Chiamiamo questi b (second_time) ec (second_time) per nostro riferimento.
  2. La funzione interna viene restituita e assegnata a Y
    A questo punto la variabile b è racchiusa nella catena dell'ambito della funzione interna come una chiusura con b (second_time) = 10, poiché inner utilizza la variabile b.
  3. La funzione esterna completa l'esecuzione e tutte le sue variabili cessano di esistere.
    La variabile c (second_time) non esiste più, anche se la variabile b (second_time) esiste come chiusura all'interno.

Ora vediamo cosa succede quando vengono eseguite le seguenti righe di codice:

X(); // X () ha invocato la prima volta
X(); // X () invocato la seconda volta
X(); // X () invocato la terza volta
Y (); // Y () ha invocato la prima volta

Quando X () viene richiamato la prima volta,

  1. la variabile a viene creata e impostata su 20
  2. il valore di a = 20, il valore di b è dal valore di chiusura. b (first_time), quindi b = 10
  3. le variabili aeb sono incrementate di 1
  4. X () completa l'esecuzione e tutte le sue variabili interne - variabile a - cessano di esistere.
    Tuttavia, b (first_time) è stato conservato come chiusura, quindi b (first_time) continua ad esistere.

Quando X () viene richiamato la seconda volta,

  1. la variabile a viene creata di nuovo e impostata su 20
     Qualsiasi valore precedente della variabile a non esiste più, poiché ha cessato di esistere quando X () ha completato l'esecuzione la prima volta.
  2. il valore di a = 20
    il valore di b è preso dal valore di chiusura - b (first_time)
    Si noti inoltre che abbiamo incrementato il valore di b di 1 rispetto all'esecuzione precedente, quindi b = 11
  3. le variabili aeb vengono nuovamente incrementate di 1
  4. X () completa l'esecuzione e tutte le sue variabili interne - variabile a - cessano di esistere
    Tuttavia, b (first_time) viene conservato mentre la chiusura continua ad esistere.

Quando X () viene richiamato la terza volta,

  1. la variabile a viene creata di nuovo e impostata su 20
    Qualsiasi valore precedente della variabile a non esiste più, poiché ha cessato di esistere quando X () ha completato l'esecuzione per la prima volta.
  2. il valore di a = 20, il valore di b è dal valore di chiusura - b (first_time)
    Si noti inoltre che avevamo incrementato il valore di b di 1 nell'esecuzione precedente, quindi b = 12
  3. le variabili aeb vengono nuovamente incrementate di 1
  4. X () completa l'esecuzione e tutte le sue variabili interne - variabile a - cessano di esistere
    Tuttavia, b (first_time) viene conservato mentre la chiusura continua ad esistere

Quando Y () viene richiamato la prima volta,

  1. la variabile a viene creata di nuovo e impostata su 20
  2. il valore di a = 20, il valore di b è dal valore di chiusura - b (second_time), quindi b = 10
  3. le variabili aeb sono incrementate di 1
  4. Y () completa l'esecuzione e tutte le sue variabili interne - variabile a - cessano di esistere
    Tuttavia, b (second_time) è stato conservato come chiusura, quindi b (second_time) continua ad esistere.

Osservazioni conclusive

Le chiusure sono uno di quei concetti sottili in JavaScript che all'inizio sono difficili da comprendere. Ma una volta che li capisci, ti rendi conto che le cose non avrebbero potuto essere diversamente.

Speriamo che queste spiegazioni dettagliate ti abbiano aiutato a capire davvero il concetto di chiusura in JavaScript!

Altri articoli:

  • Una guida rapida alle funzioni di "auto invocazione" in Javascript
  • Comprensione dell'ambito della funzione rispetto all'ambito del blocco in Javascript
  • Come utilizzare le promesse in JavaScript
  • Come costruire una semplice animazione Sprite in JavaScript