ES3, Chap. 1 — Les contextes d'exécution en JavaScript

Ce billet fait partie de la collection ES3 dans le détail et en constitue le Chapitre 1.

Comme dans Inception les contextes s'empilent les uns après les autres
Comme dans Inception les contextes s'empilent les uns après les autres

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

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érent type de code exécutable.

Ce standard ne définie 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ésumé, 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.

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étails.

Nous pouvons définir la pile des contextes d'exécution comme un tableau :

Pseudo-code

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.

Code global

Ce type de code est traité au niveau Programme : c.-à-d. 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 parti du contexte global.

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

Pseudo-code

ECStack = [
    globalContext
]

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 à cette fonction. 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 :

Code JavaScript

(function dream(flag) {
    if (flag) {
        return;
    }
    dream(true);
})(false)

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

Pseudo-code

// première activation de `dream`
ECStack = [
    <dream> functionContext,
    globalContext
]

// activation récursive de `dream`
ECStack = [
    <dream> functionContext (récursivement),
    <dream> functionContext,
    globalContext
]

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 :

Code JavaScript

(function chase() {
    (function hotel() {
        (function fortress() {
            throw 'Sort des contextes de `fortress` puis `hotel` puis `chase`';
        })();
    })();
})();

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.-à-d. 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, influence le contexte appelant :

Code JavaScript

// influence le contexte global
eval('var x = 10');

(function limbo() {
    // ici, la variable `y`
    // est créée dans le contexte local
    // de la fonction `limbo`
    eval('var y = 20');
})();

alert(x); // 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 :

Code JavaScript

function reality() {
    var x = 1;
    return function () { alert(x); };
};

var dream = reality();

dream(); // `1`

eval('x = 2', dream); // contexte passé et qui influence la variable interne `x`

dream(); // `2`

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

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éé le contexte global. Mais dans un navigateur ? Il y a « plusieurs » balises <script>, font-ils tous parti d'un même contexte global ?

En réalité il y a plusieurs contexte 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 est un code de fonction.

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 comme l'objet des variables ou la chaîne des portées dont vous trouverez plus de détails dans les chapitres appropriés.

Références

Section correspondante de la spécification ECMA-262-3 — 10. Execution Contexts.

Ce texte est une libre ré-écriture française de l'excellent billet Тонкости ECMA-262-3. Часть 1. Контексты исполнения. de Dmitry Soshnikov.