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 :
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 :
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 :
Eh bien, la pile ECStack est modifiée comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
//
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 :
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
//
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 :
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 :
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.