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"
  • Il tipo di ritorno di outerFunction è () => void, il che indica che restituisce una funzione.
  • Il tipo di innerFunction è esplicitamente impostato su void, il che indica che non restituisce alcun valore.

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 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. 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 prende un argomento di tipo number e restituisce una funzione di tipo (value: number) => number.
  • La funzione interna accetta anch'essa value come tipo number e restituisce il risultato come tipo number.

Esempio di implementazione delle Closures in TypeScript

Implementare un contatore con limiti di range come closure con annotazioni di tipo aggiuntive.

 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. L'esempio seguente mostra un caso in cui la variabile i non funziona 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