ES3 dans le détail

Les contextes d'exécution en JavaScript

Bruno Lesieur

Ce tutoriel fait partie de la collection ES3 dans le détail et en constitue le premier chapitre.

Comme dans Inception, les contextes s'empilent les uns au-dessus des autres

Dans ce tutoriel nous évoquerons les contextes d'exécution en JavaScript, ainsi que leurs différentes déclinaisons.

- Titre : ES3 dans le détail

- Auteur : Bruno Lesieur

- Parution : 17 novembre 2017

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Nom Auteur. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Définition

- II. Les différentes déclinaisons de code exécutable

- II-A. Code global

- II-B. Code de fonction

- II-C. Code eval

- III. Code global et implémentation dans les navigateurs

- III-A. Conclusion

- IV. Remerciements

I - Définition

Chaque fois que du code JavaScript est exécuté, nous entrons dans un contexte d'exécution.

Le contexte d'exécution (dont la forme abrégée sera EC pour « execution context ») est un concept abstrait décrit par la spécification ECMA-262-3 pour classifier et différentier différents types de code exécutable.

Ce standard ne définit aucune structure ni aucune déclinaison en terme d'implémentation technique des contextes d'exécution. C'est un problème qui doit être traité par les moteurs qui implémentent le standard.

Pour résumer, un groupe de contexte d'exécution forme une pile (nommée « stack »). Le bas de cette pile est toujours le contexte global (« global context ») alors que le sommet est le contexte d'exécution courant (« active context »). La pile est augmentée (« pushed ») lors de l'entrée dans un contexte d'exécution et diminuée (« popped ») lors de sa sortie.

II - Les différentes déclinaisons de code exécutable

Le concept de déclinaison de code exécutable est directement lié au concept abstrait de contexte d'exécution. Il y a le code global, le code des fonctions et le code de eval. Voyons cela plus en détail. Nous pouvons définir la pile des contextes d'exécution comme un tableau :

Pseudo-code

js ECStack = []

La pile est augmentée chaque fois que nous entrons dans une fonction (même si la fonction est appelée récursivement ou en tant que constructeur). Cela est également le cas lors de l'utilisation de la fonction intégrée eval.

II-A - Code global

Ce type de code est traité au niveau Programme : c'est-à-dire en chargeant un fichier externe .js ou un code se trouvant dans des balises <script></script>. Aucune partie de code qui se trouve à l'intérieur d'une fonction n'est du code global. Le code activé ici fait partie du contexte global.

À l'initialisation (démarrage du programme), la pile ECStack ressemble à ceci :

Pseudo-code

js ECStack = [
globalContext
]

II-B - Code de fonction

En entrant dans un code de fonction (quel qu'il soit), la pile ECStack est augmentée avec un nouvel élément. Il est important de préciser qu'aucune partie de code définie dans une sous-fonction n'appartient à la fonction courante. Le code activé dans des fonctions forme un contexte de fonction.

Prenons l'exemple d'une fonction qui va s'appeler elle-même de manière récursive juste une seule fois :

1. (function dream(flag) {

2. if (flag) {

3. return;

4. }

5. dream(true);

6. })(false)

Eh bien, la pile ECStack est modifiée comme suit :

Pseudo-code

1. // première activation de `dream`

2. ECStack = [

3. <dream> functionContext,

4. globalContext ]

5. // activation récursive de `dream`

6. ECStack = [

7. <dream> functionContext (récursivement),

8. <dream> functionContext,

9. globalContext

10. ]

Chaque return implicite ou explicite d'une fonction fait quitter le contexte d'exécution courant et diminue la pile ECStack en conséquence, refermant la pile de haut en bas. Une fois que l'exécution de ces codes est terminée, ECStack contient de nouveau uniquement le globalContext jusqu'à ce que le programme se termine. Une exception levée, mais non interceptée, peut aussi mettre fin à un ou plusieurs contextes d'exécution :

1. (function chase() {

2. (function hotel() {

3. (function fortress() {

4. throw 'Sort des contextes de `fortress` puis `hotel` puis `chase`';

5. })();

6. })();

7. })();

II-C - Code eval

Les choses sont plus intéressantes avec du code eval. Dans ce cas, il y a un concept de contexte appelant (« calling context »), c'est-à-dire un contexte depuis lequel la fonction eval est appelée.

Les actions réalisées par eval, comme la définition de variable ou de fonction, influencent le contexte appelant :

1. // influence le contexte global

2. eval('var x = 10');

3. (function limbo() {

4. // ici, la variable `y`

5. // est créée dans le contexte local

6. // de la fonction `limbo`

7. eval('var y = 20');

8. })();

9. alert(x); // `10`

10. alert(y); // y n'est pas défini(e)

Notons qu'en mode strict à partir de ES5, eval n'influence plus le contexte appelant, mais évalue son code dans un bac à sable local.

Pour l'exemple ci-dessus, nous avons les modifications de la pile ECStack suivantes :

Pseudo-code

ECStack = [
globalContext
]

// eval('var x = 10');
ECStack.push({
context: evalContext,
callingContext: globalContext
})
// Sortie du contexte `evalContext`
ECStack.pop()

// appel de la fonction `limbo`
ECStack.push(<limbo> functionContext);

// eval('var y = 20');
ECStack.push({
context: evalContext, callingContext: <limbo> functionContext
})

// Sortie du contexte `evalContext`
ECStack.pop()

// sortie de foo
ECStack.pop()

Ceci est une représentation de la logique de la pile d'appels (« call-stack »).

Dans de vieilles implémentations de Mozilla Firefox, et ceux jusqu'à la version 1.7 de son moteur JavaScript (SpiderMonkey), il était possible de passer un contexte appelant en tant que second paramètre pour la fonction eval. Ainsi, quand le contexte existait toujours, il était possible d'en influencer les variables privées :

1. function reality() {

2. var x = 1;

3. return function () { alert(x); }; };

5. var dream = reality();

6. dream(); // `1`

7. eval('x = 2', dream);

9. // contexte passé et qui influence la variable interne `x`

11. dream(); // `2`

Cependant, pour des raisons de sécurité, les moteurs modernes ne permettent plus cela désormais.

III - Code global et implémentation dans les navigateurs

Nous avons vu plus haut que le code global s'exécutait au niveau Programme. Cela signifie que le code global est le code qui s'active dès l'entrée dans un script et qui crée le contexte global. Mais dans un navigateur ? Il y a « plusieurs » balises <script>. Font-elles toutes partie d'un même contexte global ?

En réalité, il y a plusieurs contextes globaux exécutés dans un navigateur. Un pour chaque script JavaScript rencontré, qu'il soit dans les balises <script> ou dans un attribut HTML demandant à être analysé en tant que JavaScript.

Notons qu'en ce qui concerne Node.js, il n'y a qu'un seul contexte global par commande exécutée avec node et c'est celui du script appelé. Ainsi, tous les appels par le mécanisme require/export à l'intérieur du script sont un code de fonction.

III-A - Conclusion

C'est le minimum théorique requis pour analyser plus en profondeur des éléments liés au fonctionnement des contextes d'exécution.

IV - Remerciements

Nous remercions Bruno Lesieur qui nous a autorisé à publier ce tutoriel.

Nous remercions également Laethy pour la mise au gabarit et ced pour la correction orthographique.