Portée et durée de vie des variables
Pour bien comprendre toute la puissance et l’intérêt des closures, il va falloir avant tout bien comprendre la portée des variables et détailler le fonctionnement interne des fonctions.
Pour rappel, nous disposons de deux contextes ou environnements de portée différents en JavaScript : le contexte global et le contexte local. Le contexte global désigne tout le code d’un script qui n’est pas contenu dans une fonction tandis que le contexte local désigne lui le code propre à une fonction.
Dans la première partie sur les fonctions, nous avons vu qu’une fonction pouvait accéder aux variables définies dans la fonction en soi ainsi qu’à celles définies dans le contexte global.
Par ailleurs, si une fonction définit une variable en utilisant le même nom qu’une variable déjà définie dans le contexte global, la variable locale sera utilisée en priorité par la fonction par rapport à la variable globale.
See the Pen
Cours JavaScript 10.3.1 by Pierre (@pierregiraud)
on CodePen.
De plus, nous avons également vu qu’il était possible d’imbriquer une fonction ou plusieurs fonctions dans une autre en JavaScript. La fonction conteneur est alors appelée fonction « externe » tandis que les fonctions contenues sont des fonction dites « internes » par rapport à cette première fonction.
On a pu noter que les fonctions internes ont accès aux variables définies dans la fonction externe et peuvent les utiliser durant son exécution. Le contraire n’est cependant pas vrai : la fonction externe n’a aucun moyen d’accéder aux variables définies dans une de ses fonctions internes.
See the Pen
Cours JavaScript 10.3.2 by Pierre (@pierregiraud)
on CodePen.
Placer des variables dans une fonction interne permet donc de les sécuriser en empêchant leur accès depuis un contexte externe. Cela peut être très lorsqu’on souhaite définir des propriétés dont la valeur ne doit pas être modifiée par n’importe qui.
En plus de cela, vous devez savoir que les variables ont une « durée de vie ». Une variable définie dans le contexte global n’existera que durant la durée d’exécution du script puis sera écrasée. Une variable définie dans un contexte local n’existera que durant la durée d’exécution de la fonction dans laquelle elle est définie… à moins d’étendre sa durée de vie en utilisant une closure.
Les closures en pratique
Une closure est une fonction interne qui va « se souvenir » et pouvoir continuer à accéder à des variables définies dans sa fonction parente même après la fin de l’exécution de celle-ci.
Pour bien comprendre comment cela fonctionne, prenons l’exemple utilisé classiquement pour expliquer le fonctionnement des closures : l’exemple d’un compteur.
See the Pen
Cours JavaScript 10.3.3 by Pierre (@pierregiraud)
on CodePen.
Comme vous le voyez, on crée une fonction compteur()
. Cette fonction initialise une variable count
et définit également une fonction anonyme interne qu’elle va retourner. Cette fonction anonyme va elle-même tenter d’incrémenter (ajouter 1) la valeur de let count
définie dans sa fonction parente.
Ici, si on appelle notre fonction compteur()
directement, le code de notre fonction anonyme est retourné mais n’est pas exécuté puisque la fonction compteur()
retourne simplement une définition de sa fonction interne.
Pour exécuter notre fonction anonyme, la façon la plus simple est donc ici de stocker le résultat retourné par compteur()
(notre fonction anonyme donc) dans une variable et d’utiliser ensuite cette variable « comme » une fonction en l’appelant avec un couple de parenthèses. On appelle cette variable let plusUn
.
A priori, on devrait avoir un problème ici puisque lorsqu’on appelle notre fonction interne via notre variable plusUn
, la fonction compteur()
a déjà terminé son exécution et donc la variable count
ne devrait plus exister ni être accessible.
Pourtant, si on tente d’exécuter code, on se rend compte que tout fonctionne bien :
See the Pen
Cours JavaScript 10.3.4 by Pierre (@pierregiraud)
on CodePen.
C’est là tout l’intérêt et la magie des closures : si une fonction interne parvient à exister plus longtemps que la fonction parente dans laquelle elle a été définie, alors les variables de cette fonction parente vont continuer d’exister au travers de la fonction interne qui sert de référence à celles-ci.
Lorsqu’une fonction interne est disponible en dehors d’une fonction parente, on parle alors de closure ou de « fermeture » en français.
Le code ci-dessus présente deux intérêts majeurs : tout d’abord, notre variable count
est protégée de l’extérieur et ne peut être modifiée qu’à partir de notre fonction anonyme. Ensuite, on va pouvoir réutiliser notre fonction compteur()
pour créer autant de compteurs qu’on le souhaite et qui vont agir indépendamment les uns des autres. Regardez plutôt l’exemple suivant pour vous en convaincre :
See the Pen
Cours JavaScript 10.3.5 by Pierre (@pierregiraud)
on CodePen.