cela et ScopeChain en EcmaScript

Bonjour, Habr!

Dans un article précédent, nous avons examiné la théorie générale de la POO appliquée à EcmaScript et le sophisme populaire des développeurs novices concernant les différences entre la POO en JS et les langages classiques.

Aujourd'hui, nous allons parler de deux autres concepts EcmaScript tout aussi importants, à savoir la relation de l'entité avec le contexte d'exécution (c'est cette connexion) et la relation de l'entité avec le contexte de génération ( ScopeChain ).

Commençons donc!

ça


Lors des entretiens en réponse à la question: "Dites-nous en plus à ce sujet.". Les développeurs débutants, en règle générale, donnent des réponses très vagues: "c'est l'objet" avant le point "qui a été utilisé pour appeler la méthode", "c'est le contexte dans lequel la fonction a été appelée", etc. ...

En fait, la situation avec ce concept, qui est central pour EcmaScript, est un peu plus compliquée. Voyons cela dans l'ordre.

Supposons que nous ayons un programme JavaScript dans lequel il existe des variables déclarées globalement; fonctions globales; fonctions locales (déclarées à l'intérieur d'autres fonctions), fonctions renvoyées par les fonctions.

const a = 10; const b = 20; const x = { a: 15, b: 25, } function foo(){ return this.a + this.b; } function bar () { const a = 30; return a + b; } function fooBaz(){ function test () { return this.a + this.b; } return test(); } function fooBar() { const a = 40; const b = 50; return function () { return a + b; } } fooBar()(); 

Lors du transfert du contrôle vers le code exécutable, une entrée est effectuée dans le contexte d'exécution. Code exécutable - il s'agit de tout code que nous exécutons à un moment donné, il peut s'agir d'un code global ou d'un code de n'importe quelle fonction.

Le contexte d'exécution est une abstraction qui typifie et délimite le code. Du point de vue de cette abstraction, le code est divisé en code global (tous scripts connectés, scripts en ligne) et code fonction (le code des fonctions imbriquées n'appartient pas au contexte des fonctions parents).

Il existe un troisième type - EvalCode. Dans cet article, nous le négligeons.

Logiquement, l'ensemble des contextes d'exécution est une pile qui fonctionne sur le principe du Last-in-First-out (lifo). Le bas de la pile est toujours le contexte global et le haut est l'exécutable actuel. Chaque fois qu'une fonction est appelée, une entrée est effectuée dans son contexte. Lorsqu'une fonction se termine, son contexte se termine. Les contextes dépensés sont supprimés de la pile séquentiellement et dans l'ordre inverse.

Jetez un œil au code ci-dessus. Nous avons un appel à la fonction fooBar en code global. Dans la fonction fooBar, nous renvoyons une fonction anonyme que nous appelons immédiatement. Les modifications suivantes se produisent avec la pile: le contexte global y pénètre - lorsque fooBar est appelé , son contexte pénètre dans la pile - le contexte fooBar se termine, retourne une fonction anonyme et est supprimé de la pile - une fonction anonyme est appelée, son contexte pénètre dans la pile - une fonction anonyme remplit, renvoie une valeur et son contexte est supprimé de la pile - à la fin du script, le contexte global est supprimé de la pile.

Le contexte d'exécution peut être représenté sous forme d'objet. L'une des propriétés de cet objet sera l'environnement lexical (LO).

L'environnement lexical contient:

  • toutes les déclarations de variables de contexte
  • toutes les déclarations de fonction
  • tous les paramètres formels de la fonction (si nous parlons du contexte des fonctions)

Lors de la saisie du contexte d'exécution, l'interpréteur analyse le contexte. Toutes les déclarations de variables et les déclarations de fonctions remontent au début du contexte. Les variables sont créées égales à indéfinies et les fonctions sont complètement prêtes à l'emploi.

c'est aussi une propriété du contexte d'exécution, mais pas du contexte lui-même, comme répondent certains intervieweurs novices! ceci est défini lors de la saisie du contexte et reste inchangé jusqu'à la fin de la durée de vie du contexte (jusqu'à ce que le contexte soit supprimé de la pile).

Dans le contexte d'exécution global, cela est déterminé par le mode strict : lorsque le mode strict est désactivé, il contient un objet global (dans le navigateur, il est mandaté au niveau supérieur dans l'objet fenêtre), avec `` utiliser strict '', cela n'est pas défini.

ceci dans le contexte des fonctions - la question est beaucoup plus intéressante!
cette fonction est déterminée par l'appelant et dépend de la syntaxe de l'appel. Par exemple, comme nous le savons, il existe des méthodes qui permettent de corriger cela de manière rigide lors de l'appel ( appeler , appliquer ) et une méthode qui vous permet de créer un wrapper avec «fixé ceci» ( bind ). Dans ces situations, nous le déclarons explicitement et sa définition ne fait aucun doute.

Avec un appel de fonction normal, la situation est beaucoup plus compliquée!

L'un des types intégrés EcmaScript, ReferenceType , nous aidera à comprendre comment cela est apposé dans les fonctions. Il s'agit de l'un des types internes disponibles au niveau de la mise en œuvre. Logiquement, il s'agit d'un objet avec deux propriétés de base (une référence à un certain objet de base pour lequel un ReferenceType est renvoyé), propertyName (une représentation sous forme de chaîne de l'identifiant de l'objet pour lequel un ReferenceType est renvoyé).

ReferenceType est retourné pour toutes les déclarations de variables, les déclarations de fonctions et les références de propriété (c'est le cas qui nous intéresse du point de vue de la compréhension).

La règle pour définir ceci pour les fonctions appelées de la manière habituelle:
Si le ReferenceType se trouve à gauche des crochets d'activation de fonction, la base de ce ReferenceType est placée dans this fonction. Si un autre type se trouve à gauche des crochets, il s'agit alors d'un objet global ou undefined (en fait null , mais comme null n'a pas de valeur spécifique du point de vue d'ecmascript, il est converti en un objet global, dont une référence peut être est égal à undefined selon le mode strict).

Regardons un exemple:

 const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo();//  10 ..    ReferenceType  base     obj const test = obj.foo;//       test();//  0 ..  test()   .test(),..  base    ,       0. 

Je pense que la méthode de définition est clairement illustrée. Considérons maintenant quelques cas moins évidents.

Expressions fonctionnelles


Revenons à notre ReferenceType pendant une seconde. Ce type a une méthode GetValue intégrée qui renvoie le vrai type de l'objet reçu via ReferenceType. Dans la zone d'expression, GetValue se déclenche toujours.

Un exemple:

 (function (){ return this;// this     undefined    strict mode })() 

Cela est dû au fait que GetValue se déclenche toujours dans la zone d'expression. GetValue renvoie un type Function, et à gauche des crochets d'activation n'est pas un ReferenceType. Rappelons notre règle pour déterminer ceci : si un autre type se trouve à gauche des crochets, alors un objet global est placé dans this ou undefined (en fait null , mais puisque null n'a pas une certaine valeur du point de vue d'ecmascript, alors il est converti en objet global , le lien vers lequel peut être égal à indéfini selon le mode strict) .

Les zones d'expression sont: affectation (=), opérateurs || ou d'autres opérateurs logiques, opérateur ternaire, initialiseur de tableau, liste séparée par des virgules.

 const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo(); //        //  ? (obj.foo)(); // ,    , GetValue   // ? (obj.foo = obj.foo)(); //        GetValue,     Fuction,   ReferenceType,   0   (   this) //  ||    ,    ..? (obj.foo || obj.foo)();// 0    ,     //  [obj.foo][0]();// 0    ,     // .. 

Situation identique dans les expressions fonctionnelles nommées. Même avec un appel récursif à cet objet global ou undefined

ces fonctions imbriquées appelées dans le parent


Aussi une situation importante!

 const x = 0; function foo() { function bar(){ return this.x; } return bar(); } const obj = {x:10}; obj.test = foo; obj.test();// undefined 

En effet, l'appel à bar() équivalent à l'appel à LE_foo.bar , et l'objet de l'environnement lexical est défini comme tel.

Fonctions constructeur


Comme je l'ai écrit ci-dessus:
cette fonction est déterminée par l'appelant et dépend de la syntaxe de l'appel.

Nous invoquons des fonctions constructeur en utilisant le nouveau mot-clé. La particularité de cette méthode d'activation de fonction est que la méthode de fonction interne [[construct]] est appelée, qui effectue certaines opérations (le mécanisme de création d'entités par les concepteurs sera discuté dans le deuxième ou le troisième article sur la POO!) Et appelle la méthode interne [[call]] , qui définit dans cette instance créée de la fonction constructeur.

Chaîne de portée


La chaîne de portée est également une propriété du contexte d'exécution comme celui-ci. Il s'agit d'une liste d'objets des environnements lexicaux du contexte actuel et de tous les contextes générateurs. C'est dans cette chaîne que la recherche de variables se produit lors de la résolution des noms d'identifiants.

Remarque: cela associe une fonction à un contexte d'exécution et ScopeChain à des contextes enfants.

La spécification indique que ScopeChain est un tableau:

  SC = [LO, LO1, LO2,..., LOglobal]; 

Cependant, dans certaines implémentations, telles que JS, la chaîne de portée est implémentée via des listes liées .

Afin de mieux comprendre ScopeChain, nous discuterons du cycle de vie des fonctions. Il est divisé entre la phase de création et la phase d'exécution.

Lorsqu'une fonction est créée, elle est affectée à la propriété interne [[SCOPE]] .
Dans [[SCOPE]] , une chaîne hiérarchique d'objets d'environnements lexicaux de contextes supérieurs (générateurs) est enregistrée. Cette propriété reste inchangée jusqu'à ce que la fonction soit détruite par le garbage collector.

Faites attention! [[SCOPE]] , contrairement à ScopeChain, est une propriété de la fonction elle-même, pas de son contexte.

Lorsqu'une fonction est appelée, son contexte d'exécution est initialisé et rempli. Le contexte est apposé avec ScopeChain = LO (de la fonction elle-même) + [[SCOPE]] (chaîne hiérarchique de LO affectant les contextes).

Résolution des noms d'identificateurs - interrogation séquentielle des objets LO dans la chaîne ScopeChain de gauche à droite. La sortie est un ReferenceType dont la propriété de base pointe vers l'objet LO dans lequel l'identifiant a été trouvé, et PropertyName sera une représentation sous forme de chaîne du nom de l'identifiant.

C'est ainsi que la fermeture est disposée sous le capot! Une fermeture est essentiellement le résultat d'une recherche dans ScopeChain pour toutes les variables dont les identifiants sont présents dans la fonction.

 const x = 10; function foo () { return x; } (function (){ const x = 20; foo();// 10 ..     <b><i>[[SCOPE]]</i></b> foo          })() 

L'exemple suivant illustre le cycle de vie [[SCOPE]] .

 function foo () { const x = 10; const y = 20; return function () { return [x,y]; } } const x = 30; const bar = foo();//   ,   foo    bar();// [10,20] .. [[SCOPE]]    foo          

Une exception importante est la fonction constructeur . Pour ce type de fonction, [[SCOPE]] pointe toujours vers un objet global.

N'oubliez pas non plus que si l'un des maillons de la chaîne ScopeChain possède un prototype, la recherche sera également effectuée dans le prototype.

Conclusion


Nous présenterons les idées clés de manière thématique:

  • c'est la relation de l'entité avec le contexte d'exécution
  • ScopeChain est la relation d'une entité avec tous les contextes d'apparition
  • ceci et ScopeChain sont des propriétés de contexte d'exécution
  • cette fonction est déterminée par l'appelant et dépend de la syntaxe de l'appel
  • ScopeChain est l'environnement lexical du contexte actuel + [[Scope]]
  • [[Scope]] - c'est une propriété de la fonction elle-même, contient une chaîne hiérarchique d'environnements lexicaux de génération de contextes

J'espère que l'article vous a été utile. Jusqu'à de futurs articles, amis!

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


All Articles