close

JavaScript : Les Closures

#1 Qu'est ce qu'une closure ? open

Une closure c'est un fragment de code qui est composé de quatre éléments. Je vais faire un exemple pour voir comment on met en place une closure, comment ça marche et ensuite plus loin dans le tuto, on verra ce que l'on peut en faire.

  1. Dans la portée globale, je déclare une variable reference. Je ne l'initialise pas. Elle me servira plus tard.
  2. Toujours dans la portée globale, je déclare maintenant une fonction que j'appelle fGlobale.
  3. Dans la portée locale de cette fonction, je déclare une variable value que j'initialise à 1.
  4. Puis, je déclare une fonction fLocale qui va faire un console.log de value. Mes quatre éléments sont là : fGlobale, fLocale, value et reference.
  5. Maintenant, je mémorise l'adresse de fLocale dans reference.
  6. Puis, j'invoque fGlobale.
  7. Puis, j'invoque fLocale en utilisant reference.
Tester le code

Qu'est-ce qu'il faut remarquer, au niveau de ce code ? Il faut remarquer que fLocale a besoin de value pour pouvoir s'exécuter. Maintenant, je l'exécute.

Une première closure

Qu'est-ce que je constate au niveau de l'exécution ? Eh bien, je constate que fLocale a été capable d'afficher la valeur de la variable value. Pourtant, cette variable n'est pas dans la portée de fLocale. Ca veut dire que cette variable est mémorisée quelque part ailleurs. Vous pouvez vous contenter de cette explication, si ça vous suffit, mais je vais quand même détailler.

Cette variable value, au moment où j'exécute fGlobale, se trouve dans le contexte d'exécution de fGlobale. Ca c'est normal. Par contre, on pourrait penser qu'à la fin de l'exécution de fGlobale le contexte d'exécution soit dépilé de la pile d'exécution, et que value ne soit plus visible. Alors, il se trouve que ce n'est pas le cas à cause de la variable reference qui pointe sur fLocale ce qui empêche la libération du contexte d'exécution.

C'est à cet endroit-là, dans le contexte d'exécution de fGlobale, que fLocale au moment de son exécution va chercher value.

Qu'est-ce qui peut faire partie d'une closure ? Toutes les variables locales et tous les arguments de la fonction englobante (* ici fGlobale) peuvent faire partie de la closure. Le this et le tableau arguments ne peuvent pas en faire partie.

Par contre, variables ou arguments ne seront dans la closure, que s'ils sont utilisés dans la fonction locale (* ici fLocale). On va regarder ça :

  1. Je vais maintenant ajouter une variable value2 à fLocale.
  2. Je vais aussi ajouter un console.log(value2) de manière que value2 fasse partie de l'environnement d'exécution de fLocale.
  3. Je vais ajouter une instruction debugger de manière à mettre un point d'arrêt software (* tester sur Chrome).
Tester le code

On constate que value et value2 font partie de la closure. On peut remarquer aussi que la closure s'appelle fGlobale.

Exécution du script au debugger
Closure au debugger

Si je supprime le console.log(value2) alors value2 ne fait plus partie de la closure. C'est logique, car dans ces conditions fLocale n'en a pas besoin pour son exécution.

Tester le code
Exécution du script au debugger
Il faut que la variable soit utilisée pour faire partie de la closure

#2 Comment encapsuler une variable open

En fait, je vais vous montrer comment encapsuler une variable dans ce que j'appelle un module. Alors qu'est-ce que j'entends par module ici ? Il s'agit d'une technique à base de closures qui a été popularisée par Douglas Crockford, un programmeur très célèbre, et qu'on utilisait avant que les modules JavaScript avec des import/export ne soient disponibles. Mais ça s'utilise encore.

  1. Je déclare une fonction anonyme et dedans, je déclare la variable que je veux encapsuler.
  2. Ensuite, je fais directement le return d'un objet qui contient deux méthodes : une méthode set et une méthode get.
  3. Dans la méthode set, je mets une contrainte sur la valeur de l'argument the_value. Ca c'est pour bien montrer l'intérêt de l'encapsulation.
  4. Dans le get, je fais juste un return.
  5. Maintenant, cette fonction anonyme, je vais l'auto invoquer et je vais l'affecter à une variable value qui va recevoir l'objet qui sera retourné. Donc, je déclare cette variable avec un const.
Tester le code
Exécution du script
Code d'un module fait avec une closure

Faites bien la différence entre le const value qui est de type object et qui se trouve dans la portée globale et le let value qui est un Number et qui lui est local à la fonction anonyme. Ces deux value ne sont pas les mêmes et ils sont dans des environnements lexicaux différents. J'ai fait exprès de mettre le même nom pour créer cette petite difficulté et pour pouvoir l'expliquer.

L'intérêt de cette technique, c'est que vous êtes obligé de passer par les méthodes set et get pour manipuler le value du let value. En fait, la variable est encapsulée. Vous voyez que j'ai mis une contrainte sur les valeurs qui peuvent être affectées à value. Et il n'est pas possible de passer outre cette contrainte. C'est tout l'intérêt de cette construction.

#3 Plusieurs instances d'une closure open

  1. Je crée deux boites avec deux div. Je place une classe box pour les propriétés communes à ces deux div et deux id pour les propriétés spécifiques à chacune d'entre elles.
  2. Je les style un petit peu. Je les place en relative pour que les boites soient positionnées et que je puisse les décaler. Ensuite, je mets une taille, une marge en dessous et je centre le contenu. Puis je mets deux couleurs de background différentes.
  3. Maintenant, je passe au JavaScript, je déclare une fonction animation qui prend deux paramètres : le premier eltId, c'est l'id qui concerne la boite, le second vit, est un paramètre de vitesse.
  4. Je récupère un pointeur elt vers la boîte dans l'arbre. J'initialise une variable pos à 0. Dans pos je vais accumuler le décalage de la boîte. Puis j'appelle setInterval, et je lui passe comme callback une fonction fléchée.
  5. Dans cette fonction fléchée, je fais des pos += vit pour augmenter le décalage de ma boîte. Et je m'arrête lorsque je dépasse 100 pixels.
  6. Ensuite, j'appelle deux fois ma fonction animation. Un appel pour chaque boîte.
Tester le code

Je regarde ce que ce script donne à l'exécution puis je vais détailler le code.

Exécution du script
Animation avec deux instances d'une closure
  1. Il y a deux appels à la fonction animation de manière à avoir deux instances de la closure.
  2. Pour chaque appel, il y a un couple indépendant de variables elt et pos qui est alloué en pile d'exécution dans chaque closure. C'est ce que je veux dire par plusieurs instances d'une closure.
  3. Chaque callback passé en argument à l'appel à setInterval travaille sur son couple de variables.
  4. Notez également que c'est le console.log("C'est parti") qui s'exécute en premier. Les appels aux callbacks sont asynchrones et démarrent 100ms plus tard alors que le script semble terminé.
  5. Je donne deux valeurs différentes à vit au moment des deux appels à animation. Ceci permet de prouver qu'il y a deux valeurs de pos bien indépendantes si toutefois vous aviez encore un doute.