open

Javascript : Les Closures

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

L'explication que je vais vous donner est valable dans la portée globale mais nous allons nous placer dans la portée locale d'une fonction car c'est le cas que l'on rencontre et que l'on utilise le plus souvent.

Dans l'exemple suivant :

  1. Nous déclarons une variable reference qui va nous servira au point 5.
  2. Dans la portée globale nous déclarons une fonction que nous appelons fExterne.
  3. Dans la portée locale de cette fonction nous déclarons une variable vLocale. Puis nous déclarons une fonction fInterne qui va faire un console.log de vLocale.
  4. Nous invoquons fExterne.
  5. Nous invoquons fInterne par l'intermédiaire de reference.

Maintenant la question à 100 millions de dollars est : comment se fait-t-il que reference puisse accéder à la variable vLocale puisque qu'elle n'a pas la visibilité sur cette variable ?

Ca veut dire que quelque part la valeur de la variable vLocale est restée mémorisée. Et où ? En première approximation je vais répondre dans une closure.

Tester le code

OK mais c'est un peu léger comme explication. Alors prenons le debugger de chrome et mettons un point d'arrêt sur le console.log() (* Cliquez gauche sur le numéro de la ligne). Puis exécutons le code. Sur le point d'arrêt on peut voir que la closure fait partie de notre scope. Notez bien qu'il s'agit de l'appel de fInterne et vous pouvez le voir au niveau de la trace de la pile des appels (* Call Stack).

Closure au debugger

On va maintenant ajouter une variable vLocale2. Si je fais un console.log de celle-ci dans fInterne alors elle fait partie de l'environnement d'exécution de fInterne et elle fera partie de la closure à ce titre (voir ci-dessous). Essayez d'enlever le console.log et vous verrez qu'elle ne sera plus dans la closure bien que déclarée dans fExterne.

Tester le code
Closure au debugger

Si maintenant j'essaie de réponse de manière un peu plus précise à la question : qu'est ce qu'une closure ?

Une closure correspond à la mémorisation de variables qui sont nécessaires à l'exécution d'une fonction. Ces variables constituent l'environnement d'exécution d'une fonction. Elles sont mémorisées dans une pile d'exécution. Elles sont donc disponibles et différentes à chaque exécution.

Alors on peut se poser d'autres questions : pourquoi sont-elles en pile d'exécution, combien de temps vont-elles y rester ? Comment sont gérés les allocations mémoire et les libérations de mémoire en Javascript ?

Tout ça nous entraînerait dans l'étude de mécanismes internes difficile à décrire et surtout difficile à démontrer. Pour bien faire il faudrait aller dans le code du moteur Javascript pour voir ce qu'il fait... Contentons nous de comprendre ce que sont les closures et voyons ce que nous pouvons en faire.

#2 Une closure avec une fonction auto invoquée open

On reprend l'exemple précédent. Et au lieu de déclarer puis de faire un appel à la fonction fExterne on va utiliser une fonction auto invoquée.

Vous trouverez ce genre de construction dans le code de certaines librairies. Ca permet de créer des variables qui sont privées. Elles ne sont visibles que dans un scope local à une fonction et ne polluent le scope global. Vous évitez surtout les conflits potentiels qui pourraient apparaître entre des déclarations utilisateurs et la librairie. Etant donné qu'il n'existe pas en Javascript de notion d'espace de nommage comme on peut le trouver dans d'autres langages par exemple PHP et bien on utilise ce système.

Tester le code

J'avais mémorisé fInterne dans reference pour l'explication. On est pas obligé de faire ça. On peut déclarer fInterne comme une expression de fonction. Vous pouvez aussi mettre à disposition fInterne par l'intermédiaire de l'objet window.

Tester le code

#3 Plusieurs instances d'une closure open

Dans l'exemple suivant :

  1. Il y a deux appels à la fonction animation.
  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 une 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.
Tester le code