W3docs

Closures

Apprenez les closures JavaScript : comment une fonction mémorise sa portée lexicale, avec des exemples sur la confidentialité des données, les fabriques de fonctions, le patron module et les pièges courants des boucles.

Comprendre les closures en JavaScript

En JavaScript, les closures sont un concept fondamental et puissant qui permet de créer des fonctions conservant des données issues de leur environnement lexical. Ce guide propose une exploration approfondie des closures, avec des explications détaillées et des exemples de code pratiques pour consolider votre compréhension.

Qu'est-ce qu'une closure ?

Une closure est la combinaison d'une fonction et de l'environnement lexical dans lequel cette fonction a été déclarée. Cet environnement comprend toutes les variables locales qui étaient dans la portée au moment de la création de la closure.

Exemple de closure

Considérons l'exemple suivant :

javascript— editable

Dans cet exemple, greetByName est une closure. Elle capture les variables greeting et name issues de la portée de la fonction greet.

Lorsque la fonction greet est appelée avec l'argument 'John', elle crée une variable locale greeting et une fonction greetByName. La fonction greet retourne ensuite la fonction greetByName. Même après que la fonction greet a fini de s'exécuter, la fonction greetByName retournée a toujours accès aux variables greeting et name. C'est parce que greetByName est une closure, ce qui signifie qu'elle mémorise l'environnement dans lequel elle a été créée.

Pourquoi utiliser les closures ?

Les closures permettent l'encapsulation des données et la création de variables privées. Elles permettent aux fonctions de conserver l'accès aux variables de leur portée englobante, même après que cette portée a fini de s'exécuter.

Créer une closure de base

Considérons l'exemple suivant :

javascript— editable

Lorsque outerFunction est invoquée, elle crée outerVariable et innerFunction, puis retourne innerFunction. La fonction innerFunction retournée conserve l'accès à outerVariable même après que outerFunction a fini de s'exécuter.

Cas d'utilisation pratiques des closures

Confidentialité des données

Les closures peuvent être utilisées pour créer des données privées inaccessibles depuis la portée globale.

javascript— editable

Dans cet exemple, count est une variable privée qui ne peut être accédée et modifiée que par la fonction anonyme retournée. À chaque appel de la fonction, elle incrémente et retourne la valeur mise à jour de count.

Un point subtil mais important : chaque appel à createCounter produit sa propre closure indépendante avec son propre count privé. Deux compteurs créés séparément ne partagent pas d'état :

javascript— editable

Chaque invocation de la fonction externe crée un nouvel environnement lexical, donc a et b incrémentent des variables distinctes.

Fabriques de fonctions

Les closures peuvent être utilisées pour créer des fabriques de fonctions qui génèrent des fonctions spécialisées.

javascript— editable

Ici, createMultiplier génère des fonctions qui multiplient un nombre donné par un multiplier spécifié. Chaque fonction retournée conserve l'accès à la valeur de multiplier grâce à la closure.

Closures dans les boucles

Les closures peuvent être délicates lorsqu'elles sont utilisées à l'intérieur de boucles. Considérons l'exemple suivant :

javascript— editable

Dans ce code, la boucle se termine, puis les trois callbacks setTimeout s'exécutent en affichant la valeur de i, qui vaut 4 après la fin de la boucle. Cela se produit car var a une portée fonctionnelle.

Pour corriger cela, utilisez let, qui a une portée de bloc :

javascript— editable
Info

Utilisez toujours let ou const au lieu de var pour éviter les problèmes de portée et garantir que les variables ont la portée appropriée.

Pourquoi la version avec let fonctionne

L'essentiel est que let crée une nouvelle liaison de i pour chaque itération de la boucle. Chaque callback setTimeout se ferme sur un i différent, et mémorise donc la valeur qui existait lors de sa propre itération. Avec var, il n'y a qu'un seul i partagé par tous les callbacks, et au moment où les minuteries se déclenchent, la boucle l'a déjà porté à 4.

Avant l'existence de let, la solution classique consistait à créer une portée fraîche par itération avec une IIFE :

javascript— editable

La fonction immédiatement invoquée copie la valeur actuelle de i dans son propre paramètre captured, donnant à chaque callback une copie privée sur laquelle se fermer.

Closures et gestion de la mémoire

Les closures peuvent provoquer des fuites mémoire si elles ne sont pas utilisées correctement. Étant donné que les closures conservent des références à leur portée externe, les variables inutilisées peuvent persister en mémoire plus longtemps que nécessaire.

Avertissement

Soyez attentif à l'utilisation de la mémoire lors de la création de closures. Assurez-vous que les closures ne capturent pas inutilement de grands objets ou des éléments DOM afin d'éviter les fuites mémoire.

Patrons de closures avancés

Patron module

Le patron module exploite les closures pour créer des membres privés et publics au sein d'une fonction.

javascript— editable

Dans cet exemple, CounterModule est une expression de fonction immédiatement invoquée (IIFE) qui retourne un object avec des méthodes publiques (increment et reset). La variable count reste privée et n'est pas accessible directement.

Closures dans les frameworks JavaScript modernes

Les closures sont particulièrement importantes dans les frameworks JavaScript modernes comme React. Dans React, les closures aident à gérer l'état et les effets de bord au sein des composants fonctionnels.

import React, { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    function increment() {
        setCount(prevCount => prevCount + 1);
    }

    return (
        <div>
            <p>{count}</p>
            <button onClick={increment}>Increment</button>
        </div>
    );
}

Dans cet exemple, useState est utilisé pour créer une variable d'état count et une fonction setCount pour la mettre à jour. La fonction increment forme une closure, capturant setCount et count depuis la portée du composant. Remarque : dans React, les closures peuvent parfois capturer un état obsolète si les dépendances ne sont pas correctement gérées dans des hooks comme useEffect.

Info

Entraînez-vous à créer et utiliser des closures dans différents contextes pour en saisir pleinement le potentiel et les limites. Elles constituent un outil essentiel dans la boîte à outils de tout développeur JavaScript.

Bonnes pratiques pour l'utilisation des closures

  1. Limiter la portée : Évitez de créer des closures à l'intérieur de boucles ou de structures profondément imbriquées sauf si nécessaire, afin de prévenir les fuites mémoire et les problèmes de performance.
  2. Minimiser les variables capturées : Ne capturez que les variables dont vous avez besoin dans la closure afin de réduire la consommation mémoire.
  3. Éviter les captures volumineuses : Ne capturez pas de grands objets ou des éléments DOM dans les closures pour éviter une rétention mémoire inutile.
  4. Utiliser let et const : Préférez toujours let ou const à var pour garantir une portée correcte et éviter les comportements inattendus.
  5. Nettoyer : Dans la mesure du possible, libérez les références capturées par les closures pour éviter les fuites mémoire, en particulier dans les applications à longue durée de vie.

Conclusion

Les closures sont une fonctionnalité puissante de JavaScript, permettant la confidentialité des données, les fabriques de fonctions et des patrons avancés comme les modules. Comprendre et utiliser les closures efficacement peut conduire à un code plus robuste et plus facile à maintenir. En maîtrisant les closures, vous renforcerez votre capacité à écrire du code JavaScript efficace, sécurisé et propre.

Pratique

Pratique
Que sont les closures en JavaScript ?
Que sont les closures en JavaScript ?
Pratique
Pourquoi une boucle 'for' utilisant 'var' avec setTimeout affiche-t-elle trois fois la même valeur finale, tandis que 'let' affiche 1, 2, 3 ?
Pourquoi une boucle 'for' utilisant 'var' avec setTimeout affiche-t-elle trois fois la même valeur finale, tandis que 'let' affiche 1, 2, 3 ?
Was this page helpful?