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 ? Et 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 si 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 à ce 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 closure 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 environnement lexicaux différents. J'ai fait exprès de mettre les mêmes noms 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 sera 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 travaillera 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.