Environnement Lexical et fermetures en EcmaScript

Bonjour, Habr!

Je n’ai rien Ă©crit depuis longtemps, beaucoup de travail sur le projet depuis quelques semaines, mais maintenant j’ai du temps libre, j’ai donc dĂ©cidĂ© de vous prĂ©senter un nouvel article.

Aujourd'hui, nous continuerons d'analyser les concepts clés d'EcmaScript, de parler de l'environnement lexical et de la fermeture. Comprendre le concept de l'environnement lexical est trÚs important pour comprendre la fermeture, et la fermeture est le fondement de tant de bonnes techniques et technologies dans le monde JS (qui est basé sur la spécification EcmaScript).

Commençons donc.

Environnement lexical (LexicalEnvironment, LO, LE)


La spécification officielle ES6 définit ce terme comme:
L'environnement lexical est un type de spécification utilisé pour résoudre les noms d'identificateurs lors de la recherche de variables et de fonctions spécifiques basées sur la structure lexicale de l'imbrication de code ECMAScript. L'environnement lexical consiste en un enregistrement de l'environnement et, éventuellement, une référence nulle à l'environnement lexical externe.
Examinons de plus prĂšs.

J'imagine l'environnement lexical comme une sorte de structure qui stocke la connexion des identifiants de contexte avec leur signification. Il s'agit d'une sorte de référentiel de variables, fonctions, classes déclarées dans le cadre de ce contexte.

Techniquement, LO est un objet avec deux propriétés:

  • enregistrement de l'environnement (c'est lĂ  que toutes les annonces sont stockĂ©es)
  • lien vers le contexte gĂ©nĂ©ratif LO.

Grùce à un lien vers le contexte parent du contexte actuel, nous pouvons, si nécessaire, obtenir un lien vers le "contexte grand-pÚre" du contexte parent, et ainsi de suite, vers le contexte global, dont la référence au parent sera nulle. De cette définition, il résulte que l'environnement lexical est la connexion de l'entité avec les contextes de son origine. Une sorte de ScopeChain dans les fonctions est un analogue de l'environnement lexical. Nous avons parlé de ScopeChain en détail dans cet article .

let x = 10; let y = 20; const foo = z => { let x = 100; return x + y + z; } foo(30);// 150.   foo    {record: {z: 30, x: 100}, parent: __parent}; // __parent      {record: {x: 10, y: 20}, parent: null} 

Techniquement, le processus de résolution des noms d'identificateurs se déroulera comme dans ScopeChain, c'est-à-dire l'interrogation séquentielle des objets dans la boucle LO se produira jusqu'à ce que l'identifiant souhaité soit trouvé. Si l'identifiant n'est pas trouvé, alors ReferenceError.

L'environnement lexical est créé et rempli au stade de la création du contexte. Lorsque le contexte actuel termine son exécution, il est supprimé de la pile d'appels, mais son environnement lexical peut continuer à vivre tant qu'il existe au moins un lien vers celui-ci. C'est l'un des avantages d'une approche moderne de la conception des langages de programmation. Je pense que ça vaut le coup d'en parler!

Organisation de la pile vs mémoire partagée dynamiquement


Dans les langages de pile, les variables locales sont stockées sur la pile, qui est reconstituée lorsque la fonction est activée, lorsque la fonction se ferme, ses variables locales sont supprimées de la pile.

Avec une organisation empilée, il ne serait pas possible de renvoyer une fonction locale à partir d'une fonction, ou d'appeler une fonction à une variable libre.

Une variable libre est une variable utilisée par une fonction, mais ce n'est ni un paramÚtre formel ni une variable locale pour cette fonction.

 function testFn() { var locaVar = 10; //     innerFn function innerFn(p) { alert(p + localVar); } return innerFn; //  } var test = testFn();//  innerFn   test();//       

Avec l'organisation de la pile, ni la recherche locaVar dans l'environnement Lexical externe ni le retour de la fonction innerFn ne seraient possibles, car innerFn est Ă©galement une dĂ©claration locale pour testFn. À la fin de testFn, toutes ses variables locales seraient simplement supprimĂ©es de la pile.

Par consĂ©quent, un autre concept a Ă©tĂ© proposĂ© - le concept de mĂ©moire allouĂ©e dynamiquement (tas, heep) + garbage collector + reference counting. L'essence de ce concept est simple: tant qu'il y a au moins une rĂ©fĂ©rence Ă  un objet, il n'est pas supprimĂ© de la mĂ©moire. Plus de dĂ©tails peuvent ĂȘtre trouvĂ©s ici .

Fermeture (fermetures)


Une fermeture est une combinaison d'un bloc de code et de données du contexte dans lequel ce bloc est généré, c'est-à-dire c'est la relation de l'entité avec les contextes générateurs à travers une chaßne de LO ou SopeChain.

Permettez-moi de citer un trĂšs bon article Ă  ce sujet:

 function person() { let name = 'Peter'; return function displayName() { console.log(name); }; } let peter = person(); peter(); // prints 'Peter' 

Lorsque la fonction personne est exécutée, JavaScript crée un nouveau contexte d'exécution et l'environnement lexical de la fonction. Une fois cette fonction terminée, elle renverra la fonction displayName et sera affectée à la variable peter.

Ainsi, son environnement lexical ressemblera Ă  ceci:

 personLexicalEnvironment = { environmentRecord: { name : 'Peter', displayName: < displayName function reference> } outer: <globalLexicalEnvironment> } 

Une fois la fonction person terminée, son contexte d'exécution est extrait de la pile. Mais son environnement lexical restera en mémoire, car l'environnement lexical de sa fonction interne displayName y fait référence. Ainsi, ses variables seront toujours disponibles en mémoire.

Lorsque la fonction peter est exécutée (qui est en fait une référence à la fonction displayName), JavaScript crée un nouveau contexte d'exécution et un nouvel environnement lexical pour cette fonction.

Son environnement lexical ressemblera donc Ă  ceci:

 displayNameLexicalEnvironment = { environmentRecord: { } outer: <personLexicalEnvironment> } 

Il n'y a pas de variable dans la fonction displayName; son enregistrement d'environnement sera vide. Pendant l'exécution de cette fonction, JavaScript va essayer de trouver la variable de nom dans l'environnement lexical de la fonction.

Puisqu'il n'y a pas de variables dans l'environnement lexical de la fonction displayName, il recherchera dans l'environnement lexical externe, c'est-à-dire l'environnement lexical de la fonction person, qui est toujours en mémoire. JavaScript trouvera cette variable et le nom est imprimé sur la console.
La caractéristique la plus importante d'une fermeture dans ES est qu'elle utilise une portée statique (dans un certain nombre d'autres langues qui utilisent la fermeture, la situation est différente).

Un exemple:

 var a = 5; function testFn() { alert(a); } (function(funArg) { var a = 20; funArg();//  5 ..  ScopeChain/LexicalEnvironment testFn   ,    = 5 })(testFn) 

Une autre propriété de fermeture importante est la situation suivante:

 var first; var second; function testFn() { var a = 10; first = function() { return ++a; } second = function() { return --a; } a = 2; first();//3 } testFn(); first();//4 second();//3 

C'est-à-dire on voit que la variable libre présente dans les fermetures de plusieurs fonctions est modifiée par référence par elles.

Conclusion


Dans le cadre de cet article, nous avons briÚvement décrit deux concepts centraux pour EcmaScript: l'environnement lexical et la fermeture. En fait, ces deux sujets sont beaucoup plus larges. Si la communauté veut obtenir une description plus approfondie des différences entre les différents types d'environnements lexicaux ou apprendre comment la v8 construit une fermeture, écrivez à ce sujet dans les commentaires.

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


All Articles