Les Fonctions de Closures en JavaScript : (Closure Function)

les closures en javascript image

 

Les Closures sont l’un des concepts les plus intéressants et les plus subtils en JavaScript.

Néanmoins Elles peuvent parfois sembler complexes à comprendre et dans quel contexte les utiliser.

Voici un article qui va déstructurer enfin ce concept à savoir :

Que ce qu’est une closure, comment elle fonctionne, et comment l’utiliser efficacement.

Qu’est-ce qu’une Closure ?

 
 

Une closure est une fonction qui capture et conserve les variables de son environnement lexical au moment où elle est créée.

En clair, une closure « se souvient » du contexte dans lequel elle a été définie, même si elle est exécutée en dehors de ce contexte.

Cependant on utilise une closure avec une fonction anonyme à l’intérieur d’une fonction nommée (voir articles sur les fonctions).

Tout d’abord Le Contexte Lexical

 

Pour bien comprendre les closures, il est crucial de comprendre ce qu’est un contexte lexical dans une fonction en Javascript.

Autrement dit en JavaScript, le contexte lexical fait référence à la manière dont les variables et les fonctions sont organisées et accessibles dans le code en fonction de leur emplacement (leur position) dans le code source.

Par exemple lorsque vous définissez une fonction dans JavaScript, elle capture le contexte lexical où elle a été créée, c’est-à-dire toutes les variables et fonctions accessibles à ce moment-là.

Ce contexte reste attaché à la fonction même lorsqu’elle est exécutée en dehors de sa portée d’origine.

Exemple de Closure en Action

 

Voyons un exemple simple pour illustrer cela :

 

function createCounter() {
    let count = 0;

    return function() {
        count++;
        return count;
    };
}

const counter1 = createCounter();
console.log(counter1());  // Affiche 1
console.log(counter1());  // Affiche 2
console.log(counter1());  // Affiche 3

 

Explication du code ci dessus :

  1. Définition de createCounter : La fonction createCounter crée une variable locale count et retourne une fonction anonyme.

  2. Création de la Closure : Lorsque createCounter est appelée, elle retourne une nouvelle fonction qui a accès à la variable count définie dans createCounter. Cette fonction anonyme est la closure.

  3. Maintien de l’État : Même après que createCounter a terminé son exécution, la fonction retournée (la closure) conserve une référence à la variable count. À chaque appel de counter1(), la variable count est incrémentée.

 

 

Maintenant Pourquoi les Closures Sont-elles Utiles ?

 
 

Les closures offrent plusieurs avantages dans la programmation JavaScript :

  1. Encapsulation : Elles permettent de cacher l’état interne d’une fonction. Par exemple, dans l’exemple précédent, la variable count n’est pas accessible directement depuis l’extérieur, ce qui garantit que l’état ne peut être modifié qu’à travers la closure.

  2. Fonctions d’usine (Factory Functions) : Les closures sont couramment utilisées pour créer des fonctions d’usine qui génèrent des fonctions personnalisées selon des paramètres.

  3. Fonctions Callback : Elles sont très utilisées dans les callbacks et les gestionnaires d’événements pour capturer l’état au moment où le callback est défini.

  4. Gestion de l’état : Elles permettent de maintenir un état persistant dans des environnements où cela pourrait autrement être difficile, comme dans les boucles ou les gestionnaires d’événements.

 

Voici des Exemples plus Avancés de Closures

 

Exemple 1 : Utiliser des Closures pour Capturer des Variables de Boucle

 

Les closures peuvent être utilisées pour capturer l’état d’une variable dans une boucle en JavaScript. Sans closure, cela peut conduire à des comportements inattendus.

 

for (var i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, i * 1000);
}
// Cela affichera 6 cinq fois, car `i` est capturé par référence et est égal à 6 après la boucle.

 
 
Pour résoudre ce problème, vous pouvez utiliser une closure à l’aide d’une fonction anonyme pour capturer la valeur actuelle de i à chaque itération :

 

 

for (var i = 1; i <= 5; i++) {
    (function(x) {
        setTimeout(function() {
            console.log(x);
        }, x * 1000);
    })(i);
}
// Cela affichera 1, 2, 3, 4, 5 respectivement.

 
 
 

Exemple 2 : Créer des Fonctions d’usine avec des Closures

 

Les fonctions d’usine sont des fonctions qui retournent d’autres fonctions configurées de manière spécifique. Les closures sont parfaites pour cela.

 
 
 

function createMultiplier(factor) { return function(number) { return number * factor; }; } const doubler = createMultiplier(2); const tripler = createMultiplier(3); console.log(doubler(5)); // Affiche 10 console.log(tripler(5)); // Affiche 15
 
Dans cet exemple, createMultiplier utilise une closure pour capturer la valeur de factor. Les fonctions doubler et tripler « se souviennent » du facteur avec lequel elles ont été créées.
 
 
 
 

Voici un Exemple sans Closure et les Erreurs Générées

 
 

Imaginons que nous ayons un scénario où nous voulons créer plusieurs boutons sur une page web, et que lorsque nous cliquons sur un bouton, il affiche son index (c’est-à-dire sa position dans la liste des boutons).

 

 

Sans Closure : Utilisation d’une Variable Globale

 
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Exemple Sans Closure</title>
</head>
<body>
    <button>Button 1</button>
    <button>Button 2</button>
    <button>Button 3</button>

    <script>

        var buttons = document.querySelectorAll('button');
        for (var index = 0; index < buttons.length; index++) {
            buttons[index].addEventListener('click', function() {
                console.log("Button index : " + index);
            });
        }
    </script>
</body>
</html>

 

Explication

  • Variable Globale index : Ici, nous avons utilisé une variable globale index pour stocker l’index du bouton.
  • Problème : Lorsque vous cliquez sur n’importe quel bouton, vous verrez toujours la même valeur affichée. Ce comportement se produit parce que la variable globale index est partagée entre toutes les fonctions de gestion d’événements, et lorsque la boucle for se termine, la valeur de index est celle du dernier index de la boucle (dans ce cas, 3, car index est incrémenté après la dernière itération).

 

Effectivement Ce Résultat est Une Erreur Logique

 

Si vous cliquez sur n’importe quel bouton, vous verrez Button index : 3 pour tous les boutons, ce qui est incorrect.

Donc l’erreur est due au fait que toutes les fonctions d’événements se réfèrent à la même variable globale index, qui a été modifiée lors de l’exécution de la boucle.

Avec une Closure : Nous Résolvons ce Problème

 

Maintenant, voyons comment faire pour utiliser une closure pour résoudre ce problème :

 
 
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Exemple Avec Closure</title>
</head>
<body>
    <button>Button 1</button>
    <button>Button 2</button>
    <button>Button 3</button>

    <script>
        var buttons = document.querySelectorAll('button');

        for (var index = 0; index < buttons.length; index++) {
            (function(i) {
                buttons[i].addEventListener('click', function() {
                    console.log("Button a index : " + i);
                });
            })(index);
        }
    </script>
</body>
</html>

 

Explication

  • Fonction Anonyme Immédiatement Invoquée (IIFE) : Nous avons utilisé une IIFE (Immediately Invoked Function Expression) pour créer une closure. Cette fonction est exécutée immédiatement et capture la valeur de index pour chaque itération.
  • Variable i : La variable i est passée en argument à l’IIFE, ce qui permet de capturer la valeur actuelle de index à chaque tour de boucle.

 

 

Le Résultat est un Comportement Correct

 
 

Maintenant, si vous cliquez sur les boutons, vous verrez :

  • Button index : 0 pour le premier bouton,
  • Button index : 1 pour le deuxième bouton,
  • Button index : 2 pour le troisième bouton.
 
 
Sans closure, l’utilisation d’une variable globale dans une boucle peut entraîner des erreurs logiques, car la variable est partagée entre toutes les itérations.
 
Au contraire en utilisant une closure, vous pouvez capturer l’état de chaque itération de la boucle, garantissant ainsi que chaque gestionnaire d’événements a la bonne valeur d’index dans une boucle Javascript.
 
Cela montre la capacité des closures pour résoudre des problèmes liés à la portée des variables.
 
 
 

Cependant autre méthode sans var

 
 

En utilisant let au lieu de var (voir article sur les variables en JavaScript) pour déclarer la variable d’index dans la boucle, vous pouvez résoudre ce problème sans avoir besoin d’utiliser de closures.

C’est parce que let a une portée de bloc (block scope), contrairement à var qui a une portée de fonction (function scope).

Exemple avec let au lieu de var

 
 
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Exemple avec let</title>
</head>
<body>
    <button>Button 1</button>
    <button>Button 2</button>
    <button>Button 3</button>

    <script>
        var buttons = document.querySelectorAll('button');

        for (let index = 0; index < buttons.length; index++) {
            buttons[index].addEventListener('click', function() {
                console.log("Button index : " + index);
            });
        }
    </script>
</body>
</html>

 

Explication

  • Portée de Bloc avec let : Lorsque vous utilisez let, la variable index est réinitialisée à chaque itération de la boucle. Cela signifie que chaque gestionnaire d’événements se voit attribuer une nouvelle instance de index qui est spécifique à cette itération de la boucle.

 

Le Comportement est Correct

Tout comme avec la solution utilisant une closure, si vous cliquez sur les boutons, vous verrez :

  • Button index : 0 pour le premier bouton,
  • Button index : 1 pour le deuxième bouton,
  • Button index : 2 pour le troisième bouton.
 
La solution Facile :
 
Utiliser let dans une boucle for est une solution simple pour le problème de portée des variables sans avoir besoin de recourir aux closures.
 
En effet grâce à la portée de bloc de let, chaque itération de la boucle dispose de sa propre instance de la variable, ce qui résout le problème des références incorrectes dans les gestionnaires d’événements.
 

Bien que les closures soient extrêmement nécessaires dans de nombreux autres cas, dans ce cas précis, l’utilisation de let offre une solution plus directe à ce problème.

Dans Quelle autre Cas ?

 
 

Les closures sont utiles pour plusieurs raisons, même lorsque des alternatives comme l’utilisation de let sont disponibles pour certains cas spécifiques.

Comme en témoignent les utilisations des closures, elles offrent des capacités uniques qui vont bien au-delà de la simple gestion de la portée des variables dans des boucles.

Voici pourquoi les closures restent essentielles et des situations où elles sont particulièrement utiles :

1. Maintien de l’État Privé

 

Les closures permettent de créer des fonctions avec un état privé qui persiste entre les appels.

Contrairement à des variables globales ou à portée de bloc, cet état est encapsulé dans la fonction et ne peut être modifié que par cette fonction.

Exemple : Compteur avec État Privé

 
function createCounter() {
    let count = 0;

    return function() {
        count++;
        return count;
    };
}

const counter1 = createCounter();
console.log(counter1());  // Affiche 1
console.log(counter1());  // Affiche 2
console.log(counter1());  // Affiche 3

const counter2 = createCounter();
console.log(counter2());  // Affiche 1 (compteur distinct)

 
 Preuve d’utilité : Ici, count est encapsulé dans la closure, ce qui permet à counter1 et counter2 de maintenir leur propre état indépendamment l’un de l’autre. Cet état ne peut pas être modifié directement depuis l’extérieur, ce qui renforce l’encapsulation.
 
 
 

2. Création de Fonctions Dynamiques

Les closures permettent de créer des fonctions qui sont configurées dynamiquement en fonction des variables d’un contexte externe.

Exemple : Fonctions Générées Dynamiquement

 
 
 
function createAdder(x) {
    return function(y) {
        return x + y;
    };
}

const add5 = createAdder(5);
const add10 = createAdder(10);

console.log(add5(2));  // Affiche 7
console.log(add10(2)); // Affiche 12

 

Preuve d’utilité : La closure ici capture la valeur de x à chaque fois que createAdder est appelée, créant ainsi des fonctions spécifiques (add5, add10) qui ajoutent un nombre prédéfini à un autre nombre. Cette flexibilité est difficile à obtenir sans closures.

3. Callbacks et Gestion d’Événements

Dans les environnements asynchrones, comme avec les callbacks ou les gestionnaires d’événements, les closures permettent de capturer l’état au moment où le callback est défini.

Exemple : Gestion d’Événements avec Capture d’État

 
 
 
function setupButton() {
    let name = "Button 1";

    document.querySelector("button").addEventListener("click", function() {
        alert("Clicked by " + name);
    });
}

setupButton();

 
 

Preuve d’utilité : Le gestionnaire d’événements capturé « se souvient » de la valeur de name au moment où setupButton a été exécuté. Cette capture est essentielle pour que le comportement soit correct, même si name était modifié ou réinitialisé par la suite.

4. Fonctions d’Ordre Supérieur

Les closures sont à la base de nombreuses fonctions d’ordre supérieur en Javascript voir cette article (fonctions qui prennent d’autres fonctions comme arguments ou retournent des fonctions), un concept clé en programmation fonctionnelle.

Exemple : Utilisation dans map, filter, reduce

 
 
const numbers = [1, 2, 3, 4, 5];
const doubles = numbers.map(function(n) {
    return n * 2;
});

console.log(doubles); // Affiche [2, 4, 6, 8, 10]

 
Preuve d’utilité : La fonction passée à map capture l’état de chaque élément du tableau numbers. Cette capture permet de transformer chaque élément de manière concise et efficace.
 
 

Se souvenir que :

 

En somme les closures sont extrêmement utiles pour encapsuler l’état, créer des fonctions dynamiques, gérer des callbacks, et pour des cas avancés de programmation fonctionnelle.

Ainsi elles ne sont pas seulement une solution à un problème de portée de variables dans les boucles (un problème que let peut résoudre), mais une caractéristique fondamentale qui permet une programmation plus sécurisé, découpé, rèutllisable et fonctionnelle.

Notamment leur Preuve de leur utilité :

C’est pourquoi les closures permettent des structures de code qui seraient autrement impossibles ou très compliquées à réaliser.

Par conséquent elles sont importantes pour gérer l’état privé, pour créer des fonctions d’usine, pour assurer des comportements corrects dans des contextes asynchrones, et pour écrire des fonctions d’ordre supérieur, qui sont des piliers de la programmation moderne en JavaScript.

Améliore ton bagage  dans l’apprentissage de JavaScript pour cela :

 

Je t’offre un Guide Bonus Exclusif

 

En allant plus loin, avec ce Guide Bonus Exclusif rien que pour Toi !

Voici un guide complet le Kit JavaScript Pro Incubator™, où tu verras des techniques pour performer en programmation Js.

Ce guide te permettra de perfectionner tes compétences et de devenir un expert JavaScript. Ne le rate pas et développe ton expertise !

En adoptant ce qu’il contient, tu rends ton apprentissage de JavaScript plus performant avec une plus grande facilité tous les jours . Voici de quoi enrichir tout de suite ton savoir-faire avec ce guide complet qui t’attends 👇 !

Quelques liens en supplément de cette article

Voici ma Chaîne YouTube sur la programmation et le métier de développeur : https://www.youtube.com/@Developpeur-Pro

Voici un Canal ou je partage sur LinkedIn des informations sur le développement : https://www.linkedin.com/company/developpeur-pro

Retrouve ici de nombreux articles sur le code et le métier de développeur : https://developpeur-pro.com/articles-developpeur

Si vous avez aimé l’article, vous êtes libre de le partager : )

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *