JavaScript pour les Jedis, épisode II : L’attaque des Closures (partie 1)
Cet article fait partie de la série “Pour les Jedis, JavaScript” d’articles consacrés à JavaScript. Si ce n’est pas déjà fait, veuillez lire les épisodes précédents.
Dans ce second épisode consacré à l’apprentissage des fondamentaux de JavaScript pour les Jedis, nous allons aborder un autre aspect offert par ce langage : les fermetures (ou Closures en anglais). Nous allons d’abord tenter de définir et comprendre les fermetures, ensuite nous verrons comment elles facilitent nettement nos développement JavaScript, en les exploitant pour résoudre des problèmes courants liés à la portée et aux contextes des fonctions.
Étroitement liées aux fonctions, dont nous avons longuement discuté lors du premier épisode, les fermetures sont un des trois piliers de JavaScript. Pour rappel, ces trois piliers étant : les objets, les fonctions et les fermetures.
Historiquement, les fermetures étaient exclusivement réservées aux langages fonctionnels “purs” (par exemple : Haskell), et c’est donc très encourageant de les avoir dans un langage dit “grand public”, comme JavaScript. Ne soyez pas très surpris de voir les fermetures dans la plupart des librairies et frameworks JavaScript ; dites-vous que si les JavaScript Jedis sont très friands des fermetures c’est parce que ces dernières permettent de simplifier de manière drastique des opérations très complexes.
Tentons donc de définir ce que sont les fermetures. Dans la suite de cet épisode, nous allons utiliser le terme Closures pour parler des fermetures.
Comment fonctionnent les Closures ?
Si nous devions définir les Closures en une phrase : une Closure est un contexte créé par la déclaration d’une fonction, et qui permet à cette dite fonction d’accéder et manipuler des variables se trouvant en dehors de sa portée. C’est assez clair j’imagine ? Disons qu’une Closure permet à une fonction foo
d’accéder à toutes les variables et fonctions qui sont dans le contexte de déclaration (et non d’invocation) de cette fonction foo
.
Prenons un simple exemple :
Dans cet exemple, nous avons déclaré une variable jedi
, et une fonction foo
dans le même contexte — dans ce cas le contexte global. Lorsque nous exécutons la fonction foo
, celle-ci a bien accès à la variable jedi
. Je parie que vous avez écrit ce genre de code une dizaine de fois sans vous rendre compte que vous étiez en train de manipuler des Closures !
“Une Closure est un contexte créé par la déclaration d’une fonction.”
Si vous pensez que cet exemple est trop simple, sûrement parce que vous avez remarqué que la variable et la fonction sont déclarées dans le contexte global qui, tant que la page est chargée, est toujours accessible et ne change pas. Prenons un exemple un peu plus intéressant.
Analysons le comportement de la fonction bar
car il est plus intéressant.
Nous exécutons la sous-fonction bar
en différé, après l’invocation de la fonction foo
, via la copie de la référence de bar
vers jedi
. Remarquez que lorsque cette sous-fonction est exécutée, le contexte créé par la fonction foo
n’est plus disponible, ce qui voudrait dire que la variable vador
n’est plus accessible ?!
Saurez-vous donc me dire quel serait le résultat de console.log(luke, vador);
?
Pensez-vous que la réponse est : luke undefined ? Alors vous serez surpris si je vous dis que non. Le résultat est bel est bien luke je suis ton père. Quelle magie a donc permis à la variable vador
d’être toujours accessible, même après la finalisation du contexte créé par la fonction foo
? La réponse est bien sûr, les Closures.
Les Closures créent donc une sorte de “bulle” avec toutes les variables et fonctions — ainsi que la liste de leurs arguments — qui sont dans le contexte de la fonction au moment de sa déclaration, ainsi cette dernière aura tout ce dont elle aura besoin lors de son invocation. Cela permet donc de sécuriser ces variables et fonctions en leur évitant d’être détruites par le ramasse miette (Garbage Collection).
A noter tout de même que cette bulle ou structure n’est pas un objet JS auquel on peut accéder ou que l’on peut inspecter ou déboguer aussi facilement. Cependant, Google Chrome a une petite feature dans les Dev Tools permettant l’inspection de ces closures.
L’utilisation des Closures présente tout de même des inconvénients : il faut bien stocker toutes ces informations en mémoire. Rappelez-vous que chaque fonction ayant accès à des informations via des Closures, doit vivre avec cette bulle que l’on pourrait qualifier de “boulet”. Toutes ces informations doivent être mises en mémoire et y rester durant toute la vie de la fonction. Un conseil donc : utilisez les Closures avec modération, et uniquement là où il y en a besoin !
Cas d’usages
Voici une liste des différents cas d’usage illustrant l’utilisation des Closures…
Variables privées (encapsulation)
Une des utilisations les plus répandues des Closures est l’encapsulation d’information en tant que “variables privées”, pour limiter la portée de ces variables. La Programmation Orientée Objet en JavaScript ne permet pas d’avoir des variables privées : des propriétés d’un objet non accessibles depuis l’extérieur.
Il existe actuellement une proposition en cours d’étude par le TC39 concernant les propriétés privées d’une Classe.
En attendant, en utilisant les Closures nous pouvons reproduire ce comportement. Voyons cela en code.
Dans cet exemple, nous avons défini une variable jedi
dans le constructeur. Comme nous l’avons vu dans le premier épisode, JavaScript limite la portée de cette variable à la fonction servant de constructeur. Afin de pouvoir accéder à cette variable depuis l’extérieur, nous avons défini deux méthodes permettant de modifier et lire cette variable.
Après invocation du constructeur, nous invoquons les deux méthodes et en résultat, nous avons bien le comportement attendu.
Nous avons donc encapsulé la variable en limitant sa portée et cela grâce aux Closures. Voici ce que cela donne avec notre schéma de bulle :
Ceci était donc un simple et rapide aperçu de ce que l’on peut faire avec les Closures et la POO en JavaScipt. Dans la seconde partie de cet épisode, nous allons explorer en détail le monde du JavaScript Orienté Objet, toujours grâce aux Closures.
Que le JS soit avec vous…