Si vous êtes un développeur JavaScript ou souhaitez en devenir un, cela signifie que vous devez comprendre les mécanismes internes d'exécution du code JS. En particulier, une compréhension de ce que sont le contexte d'exécution et la pile d'appels est absolument nécessaire pour maîtriser d'autres concepts JavaScript, tels que l'augmentation des variables, la portée et la fermeture. Le matériel, dont nous publions la traduction aujourd'hui, est consacré au contexte d'exécution et à la pile d'appels en JavaScript.

Contexte d'exécution
Le contexte d'exécution est, en termes simplifiés, un concept qui décrit l'environnement dans lequel le code JavaScript est exécuté. Le code est toujours exécuté dans un contexte.
▍ Exécuter les types de contexte
JavaScript a trois types de contextes d'exécution:
- Contexte d'exécution global. Il s'agit du contexte d'exécution par défaut de base. Si du code ne se trouve dans aucune fonction, alors ce code appartient au contexte global. Le contexte global est caractérisé par la présence d'un objet global, qui, dans le cas du navigateur, est l'objet
window
, et par le fait que le this
pointe vers cet objet global. Un programme ne peut avoir qu'un seul contexte global. - Contexte d'exécution de la fonction. Chaque fois qu'une fonction est appelée, un nouveau contexte est créé pour elle. Chaque fonction a son propre contexte d'exécution. Un programme peut avoir simultanément plusieurs contextes pour exécuter des fonctions. Lors de la création d'un nouveau contexte pour l'exécution d'une fonction, il passe par une certaine séquence d'étapes, dont nous parlerons ci-dessous.
- Le contexte d'exécution de la fonction
eval
. Le code exécuté à l'intérieur de la fonction eval
a également son propre contexte d'exécution. Cependant, la fonction eval
est utilisée très rarement, donc ici nous ne parlerons pas de ce contexte d'exécution.
Pile d'exécution
La pile d'exécution, également appelée pile d'appel, est la pile LIFO utilisée pour stocker les contextes d'exécution créés lors de l'exécution du code.
Lorsque le moteur JS commence à traiter le script, le moteur crée un contexte d'exécution global et le place sur la pile actuelle. Lorsqu'une commande pour appeler une fonction est détectée, le moteur crée un nouveau contexte d'exécution pour cette fonction et le place en haut de la pile.
Le moteur exécute une fonction dont le contexte d'exécution est en haut de la pile. Une fois la fonction terminée, son contexte est supprimé de la pile et le contrôle est transféré dans le contexte qui se trouve dans l'élément précédent de la pile.
Nous allons explorer cette idée avec l'exemple suivant:
let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context');
Voici comment la pile d'appels changera lorsque ce code sera exécuté.
État de la pile d'appelsLorsque le code ci-dessus est chargé dans le navigateur, le moteur JavaScript crée un contexte d'exécution global et le place sur la pile d'appels actuelle. Lors d'un appel à la fonction
first()
, le moteur crée un nouveau contexte pour cette fonction et le place en haut de la pile.
Lorsque la
second()
fonction
second()
est appelée à partir de la
first()
fonction
first()
, un nouveau contexte d'exécution est créé pour cette fonction et est également poussé sur la pile. Une fois que la
second()
fonction
second()
terminé son travail, son contexte est supprimé de la pile et le contrôle est transféré au contexte d'exécution situé sur la pile en dessous, c'est-à-dire au contexte de la
first()
fonction
first()
.
Lorsque la
first()
fonction
first()
ferme, son contexte est extrait de la pile et le contrôle est transféré dans le contexte global. Une fois tout le code exécuté, le moteur récupère le contexte d'exécution global de la pile actuelle.
À propos de la création de contextes et de l'exécution de code
Jusqu'à présent, nous avons parlé de la façon dont le moteur JS gère les contextes d'exécution. Parlons maintenant de la façon dont les contextes d'exécution sont créés et de ce qui leur arrive après leur création. En particulier, nous parlons de l'étape de création du contexte d'exécution et de l'étape d'exécution de code.
▍ Étape de création du contexte d'exécution
Avant l'exécution du code JavaScript, le contexte d'exécution est créé. Au cours de sa création, trois actions sont réalisées:
- Cette valeur est déterminée et
this
(cette liaison) est liée. - Le composant
LexicalEnvironment
est créé. - Le composant
VariableEnvironment
est créé.
Conceptuellement, le contexte d'exécution peut être représenté comme suit:
ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }
Cette liaison
Dans le contexte d'exécution global,
this
contient une référence à l'objet global (comme déjà mentionné, dans le navigateur c'est un objet
window
).
Dans le contexte de l'exécution d'une fonction, sa valeur dépend de la façon dont la fonction a été appelée. S'il est appelé comme méthode d'un objet, alors la valeur de
this
liée à cet objet. Dans d'autres cas,
this
lié à un objet global ou défini sur
undefined
(en mode strict). Prenons un exemple:
let foo = { baz: function() { console.log(this); } } foo.baz(); // 'this' 'foo', 'baz' // 'foo' let bar = foo.baz; bar(); // 'this' window, //
Environnement lexical
Selon
la spécification ES6, Lexical Environment est un terme utilisé pour définir la relation entre les identifiants et les variables et fonctions individuelles en fonction de la structure de l'imbrication lexicale du code ECMAScript. L'environnement lexical se compose d'un enregistrement d'environnement et d'une référence à l'environnement lexical externe, qui peut être
null
.
En termes simples, un environnement lexical est une structure qui stocke des informations sur la correspondance des identifiants et des variables. Ici, par «identificateur», on entend le nom d'une variable ou d'une fonction, et par «variable» est une référence à un objet spécifique (y compris une fonction) ou une valeur primitive.
Dans l'environnement lexical, il y a deux composantes:
- Enregistrement d'un environnement. C'est là que les déclarations de variables et de fonctions sont stockées.
- Lien avec l'environnement externe. La présence d'un tel lien indique que l'environnement lexical a accès à l'environnement lexical parent (portée).
Il existe deux types d'environnements lexicaux:
- L'environnement global (ou le contexte d'exécution global) est un environnement lexical qui n'a pas d'environnement externe. La référence d'environnement global à l'environnement externe est
null
. Dans l'environnement global (dans l'enregistrement d'environnement), des entités de langage intégrées (telles que Object
, Array
, etc.) sont disponibles qui sont associées à l'objet global, il existe également des variables globales définies par l'utilisateur. La valeur de this
dans cet environnement pointe vers un objet global. - L'environnement de la fonction dans laquelle, dans l'enregistrement d'environnement, les variables déclarées par l'utilisateur sont stockées. La référence à l'environnement externe peut indiquer à la fois un objet global et une fonction externe à la fonction en question.
Il existe deux types d'enregistrements d'environnement:
- Enregistrement d'environnement déclaratif qui stocke des variables, des fonctions et des paramètres.
- Enregistrement d'objet d'environnement utilisé pour stocker des informations sur les variables et les fonctions dans un contexte global.
Par conséquent, dans un environnement global, un enregistrement d'environnement est représenté par un enregistrement d'environnement d'objet, et dans un environnement de fonction, par un enregistrement d'environnement déclaratif.
Notez que dans l'environnement de la fonction, l'enregistrement déclaratif de l'environnement contient également l'objet
arguments
, qui stocke la correspondance entre les indices et les valeurs des arguments passés à la fonction, et des informations sur le nombre de ces arguments.
L'environnement lexical peut être représenté comme le pseudocode suivant:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // } outer: <null> } } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // } outer: < > } }
Variables d'environnement
Un environnement variable est également un environnement lexical dont l'enregistrement d'environnement stocke les liaisons créées à l'aide des commandes
VariableStatement
dans le contexte d'exécution actuel.
Puisque l'environnement des variables est également un environnement lexical, il possède toutes les propriétés décrites ci-dessus de l'environnement lexical.
Dans ES6, il existe une différence entre les composants
LexicalEnvironment
et
VariableEnvironment
. Elle consiste dans le fait que la première est utilisée pour stocker les déclarations de fonctions et de variables déclarées à l'aide des mots clés
let
et
const
, et la seconde est utilisée uniquement pour stocker les liaisons de variables déclarées à l'aide du mot clé
var
.
Prenons des exemples illustrant ce que nous venons de discuter:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
Une représentation schématique du contexte d'exécution de ce code ressemblera à ceci:
GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // c: undefined, } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // g: undefined }, outer: <GlobalLexicalEnvironment> } }
Comme vous l'avez probablement remarqué, les variables et constantes déclarées à l'aide des mots clés
let
et
const
n'ont pas de valeurs associées, et les variables déclarées à l'aide du mot clé
var
sont définies sur
undefined
.
En effet, lors de la création du contexte, le code recherche les déclarations de variables et de fonctions, tandis que les déclarations de fonctions sont stockées entièrement dans l'environnement. Les valeurs des variables, lorsque vous utilisez
var
, sont définies sur
undefined
, et lorsque vous utilisez
let
ou
const
pas initialisées.
C'est pourquoi vous pouvez accéder aux variables déclarées avec
var
avant qu'elles ne soient déclarées (bien qu'elles ne soient
undefined
), mais lorsque vous essayez d'accéder aux variables ou constantes déclarées avec
let
et
const
exécutées avant qu'elles ne soient déclarées, une erreur se produit .
Ce que nous venons de décrire est appelé «variables de levage». Les déclarations de variables «montent» au sommet de leur portée lexicale avant d'effectuer des opérations de leur affecter des valeurs.
▍ Stade d'exécution du code
C'est peut-être la partie la plus simple de ce matériau. À ce stade, les valeurs sont affectées aux variables et le code est exécuté.
Veuillez noter que si, lors de l'exécution du code, le moteur JS ne trouve pas la valeur de la variable déclarée à l'aide du mot
let
clé
let
au lieu de déclaration, il attribuera à cette variable la valeur
undefined
.
Résumé
Nous venons de discuter des mécanismes internes d'exécution du code JavaScript. Bien que pour être un très bon développeur JS, il ne soit pas nécessaire de tout savoir, si vous avez une certaine compréhension des concepts ci-dessus, cela vous aidera à mieux comprendre les autres mécanismes du langage, tels que l'augmentation des variables, la portée, courts-circuits.
Chers lecteurs! À votre avis, quoi d'autre que le contexte d'exécution et la pile d'appels sont utiles aux développeurs JavaScript?
