Introduction aux closures en Javascript

En JavaScript, une closure est une fonction qui fait référence à des variables dans la portée externe à partir de sa portée interne. La fermeture préserve la portée externe à l'intérieur de sa portée interne. Lorsque l'on est un développeur React freelance, c'est une chose que l'on est amené à rencontrer régulièrement

Pour comprendre les closures, vous devez d'abord savoir comment fonctionne la portée lexicale.

La portée lexicale en Javascript

La portée lexicale définit la portée d'une variable par la position de cette variable déclarée dans le code source. Par exemple :

let name = 'John';

function greeting() { 
    let message = 'Hi';
    console.log(message + ' '+ name);
}

Dans cet exemple :

  • La variable name est une variable globale. Elle est accessible de partout, y compris dans la fonction greeting().
  • La variable message est une variable locale qui n'est accessible que dans la fonction greeting().

Si vous essayez d'accéder à la variable message en dehors de la fonction greeting(), vous obtiendrez une erreur.

Le moteur JavaScript utilise donc la portée pour gérer l'accessibilité des variables.

Selon le scoping lexical, les scopes peuvent être imbriqués et la fonction interne peut accéder aux variables déclarées dans son scope externe. Par exemple :

function greeting() {
    let message = 'Hi';

    function sayHi() {
        console.log(message);
    }

    sayHi();
}

greeting();

La fonction greeting() crée une variable locale nommée message et une fonction nommée sayHi().

La fonction sayHi() est une fonction interne qui n'est disponible que dans le corps de la fonction greeting().

La fonction sayHi() peut accéder aux variables de la fonction externe, comme la variable message de la fonction greeting().

À l'intérieur de la fonction greeting(), nous appelons la fonction sayHi() pour afficher le message Hi.

Comprendre les closures en Javascript

Modifions la fonction greeting() :

function greeting() {
    let message = 'Hi';

    function sayHi() {
        console.log(message);
    }

    return sayHi;
}
let hi = greeting();
hi(); // still can access the message variable

Maintenant, au lieu d'exécuter la fonction sayHi() à l'intérieur de la fonction greeting(), la fonction greeting() renvoie l'objet fonction sayHi().

Notez que les fonctions sont les citoyens de première classe en JavaScript, par conséquent, vous pouvez retourner une fonction à partir d'une autre fonction.

En dehors de la fonction greeting(), nous avons attribué à la variable hi la valeur renvoyée par la fonction greeting(), qui est une référence de la fonction sayHi().

Puis nous avons exécuté la fonction sayHi() en utilisant la référence de cette fonction : hi(). Si vous exécutez ce code, vous obtiendrez le même effet que celui décrit ci-dessus.

Cependant, le point intéressant ici est que, normalement, une variable locale n'existe que pendant l'exécution de la fonction.

Cela signifie que lorsque la fonction greeting() a terminé son exécution, la variable message n'est plus accessible.

Dans ce cas, nous exécutons la fonction hi() qui fait référence à la fonction sayHi(), la variable message existe toujours.

La magie de ceci est la fermeture. En d'autres termes, la fonction sayHi() est une closure.

Une closure est une fonction qui préserve la portée externe dans sa portée interne.

Les closures dans une boucle

Dans l'exemple

for (var index = 1; index <= 3; index++) {
    setTimeout(function () {
        console.log('after ' + index + ' second(s):' + index);
    }, index * 1000);
}

la sortie est la suivante

after 4 second(s):4
after 4 second(s):4
after 4 second(s):4

Le code affiche le même message.

Ce que nous voulions faire dans la boucle est de copier la valeur de i à chaque itération au moment de l'itération pour afficher un message après 1, 2 et 3 secondes.

La raison pour laquelle vous voyez le même message après 4 secondes est que la callback passée à la setTimeout() est une fermeture. Elle se souvient de la valeur de i de la dernière itération de la boucle, qui est 4.

En outre, les trois fermetures créées par la boucle for partagent la même portée globale et accèdent à la même valeur de i.

Pour résoudre ce problème, vous devez créer une nouvelle portée de fermeture à chaque itération de la boucle.

Il existe deux solutions populaires : IIFE & le mot-clé let.

La solution avec IIFE

Dans cette solution, vous utilisez une expression de fonction immédiatement invoquée (alias IIFE) car une IIFE crée une nouvelle portée en déclarant une fonction et en l'exécutant immédiatement.

for (var index = 1; index <= 3; index++) {
    (function (index) {
        setTimeout(function () {
            console.log('after ' + index + ' second(s):' + index);
        }, index * 1000);
    })(index);
}
after 1 second(s):1
after 2 second(s):2
after 3 second(s):3

La solution avec let en ES6

Dans ES6, vous pouvez utiliser le mot-clé let pour déclarer une variable à portée de bloc.

Si vous utilisez le mot-clé let dans la boucle for, il créera une nouvelle portée lexicale à chaque itération. En d'autres termes, vous aurez une nouvelle variable index à chaque itération.

En outre, la nouvelle portée lexicale est enchaînée à la portée précédente de sorte que la valeur précédente de l'index est copiée de la portée précédente à la nouvelle.

for (let index = 1; index <= 3; index++) {
    setTimeout(function () {
        console.log('after ' + index + ' second(s):' + index);
    }, index * 1000);
}
after 1 second(s):1
after 2 second(s):2
after 3 second(s):3

En résumé

  • Le scoping lexical décrit comment le moteur JavaScript utilise l'emplacement de la variable dans le code pour déterminer où cette variable est disponible.
  • Une closure est une combinaison d'une fonction et de sa capacité à se souvenir des variables dans la portée externe.

Un commentaire ? Laissez moi un message.