Raccourcis JavaScript pour les débutants

Les fermetures sont l'un des concepts fondamentaux de JavaScript, provoquant des difficultés pour de nombreux débutants, que chaque programmeur JS devrait connaître et comprendre. Ayant une bonne compréhension des fermetures, vous pouvez écrire un code meilleur, plus efficace et plus propre. Et cela, à son tour, contribuera à votre croissance professionnelle.

Le matériel, dont nous publions la traduction aujourd'hui, est consacré à l'histoire des mécanismes internes des fermetures et à leur fonctionnement dans les programmes JavaScript.


Qu'est-ce qu'une fermeture?


Une fermeture est une fonction qui a accès à un périmètre formé par une fonction externe par rapport à elle, même après que cette fonction externe a terminé son travail. Cela signifie qu'une fermeture peut stocker des variables déclarées dans une fonction externe et des arguments qui lui sont passés. Avant de procéder, en fait, aux fermetures, nous comprendrons le concept d '«environnement lexical».

Qu'est-ce qu'un environnement lexical?


Le terme «environnement lexical» ou «environnement statique» en JavaScript fait référence à la possibilité d'accéder à des variables, des fonctions et des objets en fonction de leur emplacement physique dans le code source. Prenons un exemple:

let a = 'global';  function outer() {    let b = 'outer';    function inner() {      let c = 'inner'      console.log(c);   // 'inner'      console.log(b);   // 'outer'      console.log(a);   // 'global'    }    console.log(a);     // 'global'    console.log(b);     // 'outer'    inner();  } outer(); console.log(a);         // 'global' 

Ici, la fonction inner() a accès aux variables déclarées dans sa propre portée, dans la portée de la fonction outer() et dans la portée globale. La fonction outer() a accès aux variables déclarées dans sa propre portée et dans la portée globale.

La chaîne de portée du code ci-dessus ressemblera à ceci:

 Global { outer {   inner } } 

Notez que la fonction inner() est entourée par l'environnement lexical de la fonction outer() , qui à son tour est entourée d'une portée globale. C'est pourquoi la fonction inner() peut accéder aux variables déclarées dans la fonction outer() et dans la portée globale.

Exemples pratiques de fermetures


Considérez, avant de démonter les subtilités des circuits internes, quelques exemples pratiques.

▍ Exemple n ° 1


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

Ici, nous appelons la fonction person() , qui renvoie la fonction interne displayName() , et stockons cette fonction dans la variable peter . Lorsque, après cela, nous appelons la fonction peter() (la variable correspondante stocke en fait une référence à la fonction displayName() ), le nom Peter est affiché dans la console.

En même temps, il n'y a pas de variable displayName() dans la fonction displayName() , nous pouvons donc conclure que cette fonction peut en quelque sorte accéder à la variable déclarée dans la fonction externe à elle, person() , même après cela comment cette fonction fonctionnait. C'est peut-être parce que la fonction displayName() est en fait une fermeture.

▍ Exemple n ° 2


 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());  // 0 console.log(count());  // 1 console.log(count());  // 2 

Ici, comme dans l'exemple précédent, nous stockons le lien vers la fonction interne anonyme retournée par la fonction getCounter() dans le count variables. Puisque la fonction count() est une fermeture, elle peut accéder à la variable counter de la fonction getCount() même après que la fonction getCounter() a terminé son travail.

Notez que la valeur de la variable counter n'est pas réinitialisée à 0 à chaque appel de la fonction count() . Il peut sembler qu'il devrait être réinitialisé à 0, comme ce serait le cas lors de l'appel d'une fonction régulière, mais cela ne se produit pas.

Cela fonctionne exactement comme ça car chaque fois que la fonction count() est appelée, une nouvelle étendue est créée pour elle, mais il n'y a qu'une seule étendue pour la fonction getCounter() . Étant donné que la variable counter est déclarée dans la portée de la fonction getCounter() , sa valeur entre les appels à la fonction count() est enregistrée sans réinitialiser à 0.

Comment fonctionnent les courts-circuits?


Jusqu'à présent, nous avons parlé des fermetures et examiné des exemples pratiques. Parlons maintenant des mécanismes internes JavaScript qui les font fonctionner.

Afin de comprendre les fermetures, nous devons traiter avec deux concepts JavaScript cruciaux. Il s'agit du contexte d'exécution et de l'environnement lexical.

▍ Contexte d'exécution


Le contexte d'exécution est un environnement abstrait dans lequel le code JavaScript est calculé et exécuté. Lorsque du code global est exécuté, cela se produit dans le contexte d'exécution global. Le code de fonction est exécuté dans le contexte de l'exécution de la fonction.

À un moment donné, le code peut être exécuté dans un seul contexte d'exécution (JavaScript est un langage de programmation à thread unique). Ces processus sont gérés à l'aide de ce qu'on appelle la pile d'appels.

La pile d'appels est une structure de données organisée selon le principe LIFO (Last In, First Out - Last In, First Out). Les nouveaux éléments ne peuvent être placés que sur le dessus de la pile, et seuls les éléments peuvent en être supprimés.

Le contexte d'exécution actuel sera toujours en haut de la pile, et lorsque la fonction actuelle se termine, son contexte d'exécution est extrait de la pile et le contrôle est transféré dans le contexte d'exécution, qui était situé sous le contexte de cette fonction dans la pile d'appels.

Considérez l'exemple suivant pour mieux comprendre le contexte d'exécution et la pile d'appels:


Exemple de contexte d'exécution

Lorsque ce code est exécuté, le moteur JavaScript crée un contexte d'exécution global pour exécuter le code global, et lorsqu'il rencontre un appel à la fonction first() , crée un nouveau contexte d'exécution pour cette fonction et le place en haut de la pile.

La pile d'appels de ce code ressemble Ă  ceci:


Pile d'appels

Lorsque l'exécution de la first() fonction first() est terminée, son contexte d'exécution est récupéré de la pile d'appels et le contrôle est transféré au contexte d'exécution en dessous, c'est-à-dire au contexte global. Après cela, le code restant dans la portée globale sera exécuté.

â–ŤEnvironnement Lexique


Chaque fois que le moteur JS crée un contexte d'exécution pour exécuter une fonction ou un code global, il crée également un nouvel environnement lexical pour stocker les variables déclarées dans cette fonction lors de son exécution.

L'environnement lexical est une structure de données qui stocke des informations sur la correspondance des identifiants et des variables. Ici, «identificateur» est le nom d'une variable ou d'une fonction, et «variable» est une référence à un objet (cela inclut les fonctions) ou une valeur d'un type primitif.

L'environnement lexical contient deux composants:

  • Un enregistrement d'environnement est l'endroit oĂą les dĂ©clarations de variables et de fonctions sont stockĂ©es.
  • RĂ©fĂ©rence Ă  l'environnement externe - un lien qui vous permet d'accĂ©der Ă  l'environnement lexical externe (parent). Il s'agit de la composante la plus importante Ă  traiter pour comprendre les fermetures.

Conceptuellement, l'environnement lexical ressemble Ă  ceci:

 lexicalEnvironment = { environmentRecord: {   <identifier> : <value>,   <identifier> : <value> } outer: < Reference to the parent lexical environment> } 

Jetez un œil à l'extrait de code suivant:

 let a = 'Hello World!'; function first() { let b = 25;  console.log('Inside first function'); } first(); console.log('Inside global execution context'); 

Lorsque le moteur JS crée un contexte d'exécution global pour exécuter du code global, il crée également un nouvel environnement lexical pour stocker les variables et les fonctions déclarées dans la portée globale. En conséquence, l'environnement lexical de portée mondiale ressemblera à ceci:

 globalLexicalEnvironment = { environmentRecord: {     a : 'Hello World!',     first : < reference to function object > } outer: null } 

Veuillez noter que la référence à l'environnement lexical externe ( outer ) est définie sur null , car la portée globale n'a pas d'environnement lexical externe.

Lorsque le moteur crée un contexte d'exécution pour la first() fonction first() , il crée également un environnement lexical pour stocker les variables déclarées dans cette fonction lors de son exécution. Par conséquent, l'environnement lexical de la fonction ressemblera à ceci:

 functionLexicalEnvironment = { environmentRecord: {     b : 25, } outer: <globalLexicalEnvironment> } 

Le lien vers l'environnement lexical externe de la fonction est défini sur <globalLexicalEnvironment> , car dans le code source, le code de la fonction est dans la portée globale.

Veuillez noter que lorsque la fonction termine son travail, son contexte d'exécution est récupéré de la pile d'appels, mais son environnement lexical peut être supprimé de la mémoire, ou il peut y rester. Cela dépend si, dans d'autres environnements lexicaux, il existe des références à cet environnement lexical sous la forme de liens vers un environnement lexical externe.

Analyse détaillée d'exemples de travail avec fermetures


Maintenant que nous nous sommes armés de la connaissance du contexte d'exécution et de l'environnement lexical, nous reviendrons sur les fermetures et analyserons plus en profondeur les mêmes fragments de code que nous avons déjà examinés.

▍ Exemple n ° 1


Jetez un œil à cet extrait de code:

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

Lorsque la fonction person() est exécutée, le moteur JS crée un nouveau contexte d'exécution et un nouvel environnement lexical pour cette fonction. En fin de travail, la fonction renvoie la fonction displayName() , une référence à cette fonction est écrite dans la variable peter .

Son environnement lexical ressemblera Ă  ceci:

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

Lorsque la fonction person() ferme, son contexte d'exécution est extrait de la pile. Mais son environnement lexical reste en mémoire, car il existe un lien vers lui dans l'environnement lexical de sa fonction interne displayName() . Par conséquent, les variables déclarées dans cet environnement lexical restent disponibles.

Lorsque la fonction peter() est appelée (la variable correspondante stocke une référence à la fonction displayName() ), le moteur JS crée un nouveau contexte d'exécution et un nouvel environnement lexical pour cette fonction. Cet environnement lexical ressemblera à ceci:

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

Il n'y a pas de variables dans la fonction displayName() , donc son enregistrement d'environnement sera vide. Lors de l'exécution de cette fonction, le moteur JS tentera de trouver la variable de name dans l'environnement lexical de la fonction.

Étant donné que la recherche est introuvable dans l'environnement lexical de la fonction displayName() , la recherche se poursuivra dans l'environnement lexical externe, c'est-à-dire dans l'environnement lexical de la fonction person() , qui est toujours en mémoire. Là, le moteur trouve la variable souhaitée et affiche sa valeur dans la console.

▍ Exemple n ° 2


 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());  // 0 console.log(count());  // 1 console.log(count());  // 2 

L'environnement lexical de la fonction getCounter() ressemblera Ă  ceci:

 getCounterLexicalEnvironment = { environmentRecord: {   counter: 0,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 

Cette fonction renvoie une fonction anonyme qui est affectée à la variable de count .

Lorsque la fonction count() est exécutée, son environnement lexical ressemble à ceci:

 countLexicalEnvironment = { environmentRecord: { } outer: <getCountLexicalEnvironment> } 

Lors de l'exécution de cette fonction, le système recherchera la variable counter dans son environnement lexical. Dans ce cas, à nouveau, l'enregistrement d'environnement de fonction est vide, de sorte que la recherche de la variable se poursuit dans l'environnement lexical externe de la fonction.

Le moteur trouve la variable, l'affiche dans la console et incrémente la variable counter , qui est stockée dans l'environnement lexical de la fonction getCounter() .

Par conséquent, l'environnement lexical de la fonction getCounter() après le premier appel à la fonction count() ressemblera à ceci:

 getCounterLexicalEnvironment = { environmentRecord: {   counter: 1,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 

Chaque fois que la fonction count() est appelée, le moteur JavaScript crée un nouvel environnement lexical pour cette fonction et incrémente la variable counter , ce qui entraîne des changements dans l'environnement lexical de la fonction getCounter() .

Résumé


Dans cet article, nous avons parlé de ce que sont les fermetures et trié les mécanismes JavaScript sous-jacents qui les sous-tendent. Les fermetures sont l'un des concepts JavaScript fondamentaux les plus importants, et chaque développeur JS doit les comprendre. La compréhension des fermetures est l'une des étapes de l'écriture d'applications efficaces et de haute qualité.

Chers lecteurs! Si vous avez de l'expérience en développement JS, veuillez partager des exemples pratiques d'utilisation des fermetures avec des débutants.

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


All Articles