Contexte d'exécution JavaScript et pile d'appels

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'appels

Lorsque 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:

  1. Cette valeur est déterminée et this (cette liaison) est liée.
  2. Le composant LexicalEnvironment est créé.
  3. 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:

  1. Enregistrement d'un environnement. C'est là que les déclarations de variables et de fonctions sont stockées.
  2. 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:

  1. 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.
  2. 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:

  1. Enregistrement d'environnement déclaratif qui stocke des variables, des fonctions et des paramètres.
  2. 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?

Source: https://habr.com/ru/post/fr422089/


All Articles