Une closure, c'est une fonction qui se souvient
Dès que tu définis une fonction en JavaScript, elle garde discrètement un lien vers les variables qui l'entourent. Plus tard, même si elle est appelée ailleurs, elle a toujours accès à ces variables. C'est ça, une closure en JavaScript.
La démo la plus courte :
makeGreeter s'exécute, renvoie une fonction interne, puis se termine. On pourrait croire que sa variable locale name disparaît — après tout, la fonction a fini son travail. Sauf que la fonction interne utilise encore name, donc JavaScript la garde en vie. greetAda se souvient de "Ada". greetBoris se souvient de "Boris". Deux closures, deux valeurs mémorisées bien distinctes.
La portée lexicale, à l'origine des closures
La règle qui fait fonctionner les closures s'appelle la portée lexicale (lexical scope en anglais) : une fonction voit les variables de l'endroit où elle a été écrite, pas de l'endroit où on l'appelle. Le mot « lexicale » veut simplement dire « en fonction de sa position dans le code source ».
show affiche "Je suis à l'extérieur", pas "Je suis dans caller". Elle a été écrite à côté du outer de plus haut niveau, donc c'est celui-là qu'elle voit. Qu'on l'appelle depuis un endroit qui a son propre outer n'y change rien.
Une closure, ce n'est rien d'autre qu'une portée lexicale qui survit à la fonction englobante. La variable ne disparaît pas tant que quelqu'un garde une référence dessus.
Chaque appel crée sa propre closure
Chaque nouvel appel à la fonction externe crée de nouvelles variables, et toute fonction interne retournée par cet appel se souvient de ces variables-là. C'est pour ça que greetAda et greetBoris plus haut ne se marchaient pas sur les pieds.
L'exemple classique, c'est le compteur :
a et b possèdent chacun leur propre count. Rien en dehors de la fonction retournée ne peut toucher à ces variables — count est totalement privé. Ce n'est pas une option qu'on a activée dans le langage : c'est une conséquence directe du fonctionnement des closures.
Variables privées en JavaScript sans passer par les classes
Comme les variables capturées ne sont accessibles qu'à travers la fonction retournée, on peut se servir des closures pour construire de petits objets dont l'état est réellement privé :
balance n'est pas une propriété de l'objet retourné — elle vit dans la closure. Le seul moyen de la lire ou de la modifier, c'est de passer par les méthodes que tu as exposées. Les classes avec les champs #private peuvent faire la même chose, mais la version à base de closure existe depuis des décennies et on la croise encore partout dans l'écosystème.
Le piège classique : closure dans une boucle
C'est dans les boucles que les closures piègent le plus de monde. Regarde ce qui se passe avec var :
Logiquement, on s'attend à voir 0, 1, 2. Sauf qu'on obtient 3, 3, 3. Pourquoi ? Parce que var a une portée de fonction : il n'existe qu'un seul i pour toute la boucle. Les trois closures ont donc capturé la même variable, et au moment où elles s'exécutent, la boucle est déjà terminée avec i à 3.
Il suffit de passer à let :
Du coup, la console affiche 0, 1, 2. let a une portée de bloc — à chaque tour de boucle, une nouvelle liaison de i est créée, et chaque closure capture donc sa propre valeur. C'est de loin la meilleure raison de préférer let à var.
Les closures capturent des variables, pas des valeurs
Un détail subtil mais fondamental : une closure conserve une référence à la variable elle-même, et non un instantané de sa valeur au moment où la fonction a été définie.
printMessage lit la valeur de message au moment où elle s'exécute, pas au moment où elle a été créée. Si tu veux figer la valeur, copie-la d'abord dans une variable locale — ce qui correspond exactement à ce que fait let à l'intérieur d'une boucle for.
Un cas concret très courant : la fonction Once
Voici un petit utilitaire qui s'appuie sur une closure pour garantir qu'une fonction ne s'exécute qu'une seule fois :
called et result sont des variables privées qui vivent aussi longtemps que la fonction retournée. Pas besoin de drapeau global, pas d'objet en plus. Ce motif — petite fonction utilitaire, état privé, closure — c'est l'une des choses les plus pratiques que JavaScript sait faire.
Un mot sur la mémoire
Une closure garde en vie les variables qu'elle a capturées tant que quelque chose référence encore la closure. La plupart du temps c'est exactement ce qu'on veut, mais attention : si tu attaches une closure à quelque chose qui dure longtemps (un écouteur d'événement DOM, un cache global…) et qu'elle capture un gros objet, le garbage collector ne pourra pas libérer cet objet tant que la closure traîne. C'est comme ça qu'on se retrouve avec une fuite mémoire.
function attach() {
const hugeData = new Array(1_000_000).fill("...");
document.addEventListener("click", () => {
console.log(hugeData.length);
});
}
Tant que l'écouteur reste attaché, hugeData reste en mémoire. Supprimez l'écouteur (ou évitez de capturer ce dont vous n'avez pas besoin) et la référence se libère. Pas besoin de micro-gérer tout ça — retenez simplement que les closures et la mémoire sont liées.
Ce qu'il faut retenir
- Une closure, c'est une fonction accompagnée des variables qu'elle voyait au moment de sa définition.
- Chaque appel à une fonction englobante crée un nouveau jeu de variables pour ses closures internes.
- Les closures permettent d'avoir un état privé sans passer par des classes.
- Dans une boucle, utilisez
letpour que chaque itération ait sa propre liaison. - Les closures capturent la variable elle-même, pas la valeur au moment de la création.
La suite : le mot-clé this
Les closures s'occupent des variables qui entourent une fonction. L'étape suivante, c'est sur quoi une fonction est appelée — en JavaScript, c'est le rôle de this, et son comportement n'a pas grand-chose à voir avec les variables capturées que l'on vient de voir.
Questions fréquentes
C'est quoi une closure en JavaScript ?
Une closure, c'est une fonction qui garde en mémoire les variables de la portée dans laquelle elle a été définie, même après que cette portée externe a terminé son exécution. Techniquement, toutes les fonctions JavaScript sont des closures — mais on en parle surtout quand une fonction est retournée ou passée ailleurs tout en continuant d'utiliser les variables de son contexte d'origine.
À quoi servent les closures concrètement ?
Elles permettent à une fonction de transporter son propre état privé. Tu obtiens des données liées à une fonction sans avoir besoin d'une classe ni d'une variable globale. Les cas classiques : compteurs, callbacks à usage unique, fonctions mémoïsées, ou encore masquer des détails d'implémentation derrière une petite API.
Pourquoi les closures se comportent bizarrement dans une boucle avec var ?
var a une portée de fonction, donc toutes les itérations partagent la même variable. Les closures créées dans la boucle pointent toutes vers cette unique variable, qui a déjà atteint sa valeur finale au moment où elles s'exécutent. La solution : utiliser let, qui a une portée de bloc — chaque itération obtient alors sa propre liaison.