Closure in TypeScript

Closure in TypeScript

In questo articolo, spiegheremo le closures in TypeScript.

YouTube Video

Closure in TypeScript

Cos'è una Closure?

Una closure si riferisce alla capacità di mantenere i riferimenti allo scope (ambiente) in cui una funzione è stata definita, anche se la funzione viene chiamata al di fuori di quello scope. Di seguito, le closures verranno spiegate includendo le annotazioni di tipo.

In parole semplici, una closure combina una funzione e l'ambiente delle variabili in cui la funzione è stata definita, permettendo di accedere a quell'ambiente quando la funzione viene chiamata.

Meccanismo di base delle Closures

In TypeScript, quando una funzione viene definita all'interno di un'altra funzione, diventa evidente che la funzione interna può accedere alle variabili della funzione esterna. Ecco un esempio base di closure con annotazioni di tipo.

 1function outerFunction(): () => void {
 2    let outerVariable: string = "I am from outer function";
 3
 4    function innerFunction(): void {
 5        // The inner function accesses the variable of the outer function
 6        console.log(outerVariable);
 7    }
 8
 9    return innerFunction;
10}
11
12const closure: () => void = outerFunction();
13closure();  // "I am from outer function"
  • outerFunction restituisce la funzione interna innerFunction.
  • innerFunction mostra il valore della variabile outerVariable della funzione esterna`.

Utilizzi e vantaggi delle Closures

Incapsulamento dei dati

Di seguito è riportato un esempio di closure con annotazioni di tipo per l’incapsulamento dei dati.

 1function createCounter(): () => number {
 2    let count: number = 0;
 3
 4    return function (): number {
 5        count += 1;
 6        return count;
 7    };
 8}
 9
10const counter: () => number = createCounter();
11console.log(counter());  // 1
12console.log(counter());  // 2
13console.log(counter());  // 3
  • La funzione createCounter restituisce una funzione che restituisce un valore di tipo number.
  • La variabile count è definita come tipo number e viene manipolata all'interno della closure.

Funzioni di ordine superiore

Le closures sono utili nella creazione di funzioni di ordine superiore. Qui, una funzione di ordine superiore è una funzione che prende un'altra funzione come argomento oppure restituisce una funzione come risultato. Di seguito è riportato un esempio di funzione di ordine superiore con annotazioni di tipo chiare.

 1function createMultiplier(multiplier: number): (value: number) => number {
 2    return function (value: number): number {
 3        return value * multiplier;
 4    };
 5}
 6
 7const double: (value: number) => number = createMultiplier(2);
 8console.log(double(5));  // 10
 9
10const triple: (value: number) => number = createMultiplier(3);
11console.log(triple(5));  // 15
  • createMultiplier è una funzione di ordine superiore che crea una funzione per moltiplicare per il numero che riceve come argomento.
  • La funzione interna di moltiplicazione prende anch'essa un numero e restituisce il risultato della moltiplicazione come numero.

Esempio di implementazione delle Closures in TypeScript

Vediamo anche un esempio di implementazione di un contatore basato su intervallo come chiusura.

 1function rangeCounter(min: number, max: number): () => number | string {
 2    let count: number = min;
 3
 4    return function (): number | string {
 5        if (count <= max) {
 6            return count++;
 7        } else {
 8            return `Count has exceeded the maximum value: ${max}`;
 9        }
10    };
11}
12
13const counter: () => number | string = rangeCounter(1, 5);
14
15console.log(counter());  // 1
16console.log(counter());  // 2
17console.log(counter());  // 3
18console.log(counter());  // 4
19console.log(counter());  // 5
20console.log(counter());  // "Count has exceeded the maximum value: 5"
  • La funzione rangeCounter restituisce una funzione che restituisce un number o una string.
  • Nella funzione interna, se count supera max, restituisce un messaggio di tipo string; altrimenti, restituisce un valore di tipo number.

Precauzioni nell'uso delle Closures

Possibili memory leak dalle Closures

Le closures possono mantenere le variabili da uno scope esterno, cosa che a volte può causare memory leak. Le closures non necessarie devono essere esplicitamente rilasciate dalla memoria.

 1function createLeak(): () => void {
 2    // Large array consuming significant memory
 3    const largeArray: string[] = new Array(1000000).fill("leak");
 4
 5    // Closure capturing `largeArray`
 6    return function (): void {
 7        console.log(largeArray[0]); // Using the captured array
 8    };
 9}
10
11// Create a closure that holds a reference to the large array
12let leakyFunction = createLeak();
13
14// The large array is not released as `leakyFunction` still references it
15
16// When the object is no longer needed
17leakyFunction = null; // Explicitly remove the reference
  • In questo codice, l’array largeArray creato all’interno di createLeak dovrebbe essere rilasciato quando esce dallo scope, ma non accade perché la closure cattura largeArray. Finché leakyFunction esiste, questa memoria non necessaria continuerà ad essere trattenuta.
  • Quando un oggetto o una variabile non sono più necessari, impostarne il riferimento a null permette al garbage collector di rilevarlo e liberare la memoria.

Uso improprio delle Closures nei cicli

Quando si creano closures all'interno di un ciclo, possono sorgere problemi riferendosi alla stessa variabile. Il seguente esempio mostra un caso in cui la variabile i, dichiarata con var, non si comporta correttamente.

1for (var i: number = 0; i < 3; i++) {
2    setTimeout((): void => {
3        console.log(i);
4    }, 1000);
5}
6// Output: 3, 3, 3

Questo codice non produce il risultato desiderato perché i si riferisce al valore 3 alla fine del ciclo. Per risolvere questo problema, usa let per separare lo scope oppure utilizza una funzione invocata immediatamente.

1for (let i: number = 0; i < 3; i++) {
2    setTimeout((): void => {
3        console.log(i);
4    }, 1000);
5}
6// Output: 0, 1, 2

Utilizzando let, lo scope di i viene separato ad ogni iterazione del ciclo, ottenendo i risultati attesi.

Riepilogo

In TypeScript, le closures possono rendere il codice più sicuro e prevedibile sfruttando il sistema di tipi. Un uso corretto delle closures consente l'incapsulamento dei dati e la progettazione flessibile di funzioni di ordine superiore. Inoltre, occorre prestare attenzione alla gestione della memoria e ai riferimenti di scope indesiderati durante l’uso delle closures.

Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.

YouTube Video