Fermeture (Closure) en TypeScript

Fermeture (Closure) en TypeScript

Dans cet article, nous allons expliquer les fermetures (closures) en TypeScript.

YouTube Video

Fermeture (Closure) en TypeScript

Qu'est-ce qu'une fermeture (closure) ?

Une fermeture (closure) désigne la capacité à conserver des références à l'environnement (scope) dans lequel une fonction a été définie, même lorsque cette fonction est appelée en dehors de cet environnement. Ci-dessous, les fermetures (closures) seront expliquées avec les annotations de type.

Pour faire simple, une fermeture (closure) associe une fonction à l’environnement de variables où elle a été définie, permettant d'y accéder lorsque la fonction est appelée.

Mécanisme de base des fermetures (closures)

En TypeScript, lorsqu’une fonction est définie à l’intérieur d’une autre fonction, il devient évident que la fonction interne peut accéder aux variables de la fonction externe. Voici un exemple simple d’une fermeture (closure) avec des annotations de type.

 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 renvoie la fonction interne innerFunction.
  • innerFunction affiche la valeur de la variable outerVariable de la fonction externe.

Utilisations et avantages des fermetures (closures)

Encapsulation des données

Ci-dessous un exemple de fermeture (closure) avec des annotations de type pour encapsuler des données.

 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 fonction createCounter renvoie une fonction qui retourne une valeur de type number.
  • La variable count est de type number et est manipulée à l’intérieur de la fermeture (closure).

Fonctions d’ordre supérieur

Les fermetures (closures) sont utiles lors de la création de fonctions d’ordre supérieur. Ici, une fonction d'ordre supérieur est une fonction qui prend une autre fonction comme argument ou qui retourne une fonction comme résultat. Voici un exemple d’une fonction d’ordre supérieur avec des annotations de type claires.

 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 est une fonction d'ordre supérieur qui crée une fonction permettant de multiplier par le nombre qu'elle reçoit en argument.
  • La fonction de multiplication interne prend également un nombre et retourne le résultat de la multiplication sous forme de nombre.

Exemple d’implémentation de fermetures (closures) en TypeScript

Voyons également un exemple d'implémentation d'un compteur basé sur une plage en tant que fermeture.

 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 fonction rangeCounter retourne une fonction qui retourne soit un number, soit une string.
  • Dans la fonction interne, si count dépasse max, elle retourne un message de type string ; sinon, elle retourne un number.

Précautions lors de l’utilisation de fermetures (closures)

Fuites de mémoire potentielles dues aux fermetures (closures)

Les fermetures (closures) peuvent conserver des variables provenant d’un scope externe, ce qui peut parfois provoquer des fuites de mémoire. Les fermetures (closures) inutiles doivent être explicitement libérées de la mémoire.

 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
  • Dans ce code, le largeArray créé à l’intérieur de createLeak devrait normalement être libéré lorsqu’il sort du scope, mais ce n’est pas le cas car la fermeture (closure) capture largeArray. Tant que leakyFunction existe, cette mémoire non nécessaire sera conservée.
  • Lorsqu’un objet ou une variable n’est plus nécessaire, définir sa référence à null permet au ramasse-miettes (garbage collector) de la détecter et de libérer la mémoire.

Mauvaise utilisation des fermetures (closures) dans les boucles

Lors de la création de fermetures (closures) à l’intérieur d’une boucle, il peut y avoir des problèmes de référence à la même variable. L'exemple suivant montre un cas où la variable i déclarée avec var ne se comporte pas correctement.

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

Ce code ne donne pas le résultat souhaité, car i fait référence à la valeur 3 à la fin de la boucle. Pour corriger cela, utilisez soit let pour séparer le scope, soit une fonction immédiatement invoquée.

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

En utilisant let, le scope de i est séparé à chaque itération de la boucle, produisant ainsi les résultats attendus.

Résumé

En TypeScript, les fermetures (closures) permettent d’écrire un code plus sûr et plus prévisible en profitant du système de types. Une utilisation correcte des fermetures (closures) permet l’encapsulation des données et une conception flexible des fonctions d’ordre supérieur. De plus, il convient de faire attention à la gestion de la mémoire et aux références de scope involontaires lors de l’utilisation de fermetures (closures).

Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.

YouTube Video