Comment fonctionnent les fermetures en JavaScript : analyse à partir d'un exemple de Stack Overflow
\n\nSi vous avez déjà cherché « JavaScript closures » sur Google ou lu des discussions sur Stack Overflow, vous avez sûrement vu la célèbre question : « How do JavaScript closures work? ». Ce fil a rassemblé des milliers de vues et des centaines de réponses — des explications simples aux plongées profondes dans le moteur V8. Pourquoi ? Parce que les fermetures sont l'un de ces concepts qui semblent d'abord magiques, puis deviennent votre super-arme.
\n\nDans cet article, nous ne nous contenterons pas de répéter la théorie. Nous allons regarder sous le capot de JavaScript, analyser ce qui se passe en mémoire, et enfin comprendre pourquoi ce code fonctionne exactement ainsi et pas autrement. Prêts ? C'est parti.
\n\nQu'est-ce qu'une fermeture ? Définition sans fioritures
\n\nCommençons par l'essentiel. Une fermeture (closure) est une fonction qui « se souvient » des variables de sa portée externe, même après que cette fonction externe a terminé son exécution.
\n\nCela ressemble à une incantation ? Passons tout de suite au code. Voici un exemple classique que vous trouverez sur Stack Overflow :
\n\nfunction createCounter() {\n let count = 0;\n return function() {\n count++;\n return count;\n };\n}\n\nconst counter = createCounter();\nconsole.log(counter()); // 1\nconsole.log(counter()); // 2\nconsole.log(counter()); // 3\n\nIci, createCounter retourne une fonction anonyme. Cette fonction interne « ferme » la variable count. Même après que createCounter a terminé son exécution (nous pensons que toutes ses variables devraient disparaître), count continue d'exister et de changer à chaque appel de counter(). C'est cela, une fermeture.
Sous le capot : l'environnement lexical
\n\nPour comprendre comment cela fonctionne, il faut regarder à l'intérieur du moteur JavaScript. Chaque fonction, lors de sa création, possède une propriété cachée [[Environment]]. Elle stocke une référence à l'environnement lexical (Lexical Environment) dans lequel la fonction a été créée.
L'environnement lexical est simplement un objet qui contient :
\n- \n
- Un enregistrement d'environnement (Environment Record) — toutes les variables et fonctions déclarées dans cette portée. \n
- Une référence à l'environnement externe (outer) — la portée parente. \n
Lorsque nous appelons une fonction, un nouvel environnement lexical est créé pour cet appel. La fonction interne accède aux variables de la fonction externe via la chaîne des portées (scope chain).
\n\nRevenons à notre exemple. Lorsque nous déclarons createCounter, un environnement lexical global est créé. À l'intérieur, on trouve la variable createCounter (la fonction) et counter (encore undefined).
Lorsque nous appelons createCounter() :
- \n
- Un nouvel environnement lexical est créé pour cet appel. Il contient la variable
count = 0. \n - Une fonction anonyme est créée. Son
[[Environment]]pointe vers l'environnement actuel (où vitcount). \n - La fonction est retournée et assignée à
counter. \n
Et c'est là que ça devient intéressant : createCounter a terminé son exécution, mais son environnement lexical n'est pas supprimé par le ramasse-miettes ! Car il existe une référence vers lui — le [[Environment]] de la fonction interne. L'environnement reste en mémoire tant que la fonction counter elle-même existe.
Maintenant, chaque fois que nous appelons counter(), un nouvel environnement est créé pour cet appel, qui, via la chaîne, voit cet ancien environnement contenant count. C'est ainsi que nous obtenons l'effet de « mémorisation ».
Pourquoi est-ce important ? Scénarios réels
\n\nLes fermetures ne sont pas qu'un concept académique. Elles sont utilisées partout. Analysons quelques exemples concrets.
\n\n1. Variables privées (encapsulation)
\n\nEn JavaScript, il n'y a pas de véritables