Les Closures en JavaScript

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.

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.

On utilise avec une closure avec une fonction anonyme à l’intérieur d’une fonction nommée (voir les autres 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.

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.

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. 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.

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.
 
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.
 
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 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 et élégante pour le problème de portée des variables sans avoir besoin de recourir aux closures.
 
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.

Les closures 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 (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.
 
 

A bien retenir :

 

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.

 

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.

 

Preuve de leur utilité :

 

Les closures permettent des structures de code qui seraient autrement impossibles ou très compliquées à réaliser.

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 sur le JavaScript, 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 ? Le guide complet t’attend !

 
 

Rejoignez notre Newsletter et Restez Informé !

Vous souhaitez rester à jour avec les dernières tendances et actualités du monde du développement et le métier de développeur. Comment devenir développeur pro ? Rejoignez notre newsletter pour obtenir un accès exclusif à du contenu premium, des astuces de codage, des mises à jour sur les nouvelles fonctionnalités et bien plus encore !

Avantages de l’Inscription

  • Restez Informé: Recevez des articles informatifs sur les dernières avancées et les meilleures pratiques de codage et les softkills.
  • Promos Exclusives: Accédez à des formations détaillés et à des exemples de code pour améliorer vos compétences en programmation.
  • Aperçus des Nouveautés: Soyez parmi les premiers à découvrir les nouvelles fonctionnalités et les frameworks émergents dans l’écosystème du développement FrontEnd et Backend.
  • Communauté Engagée: Rejoignez une communauté passionnée de développeurs et partagez vos idées, questions et expériences.

Comment S’Inscrire

C’est simple et rapide ! Remplissez le formulaire d’inscription avec votre adresse e-mail et cliquez sur « S’Inscrire ». Vous recevrez régulièrement notre newsletter dans votre boîte de réception.


L’inscription à notre newsletter est un moyen idéal de rester informé et de progresser dans le domaine de la programmation et du développement pour devenir un développeur professionnel ou une développeuse pro.

Laisser un commentaire

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