JavaScript sous le capot : boucle d'événement

Dec 01 2022
Maîtrisez JavaScript en explorant son fonctionnement à partir de zéro Avez-vous déjà rencontré des erreurs indéfinies ou eu du mal à identifier la portée d'une variable ? C'est vraiment long de déboguer toute erreur sans savoir comment le code fonctionnait. Dans ce blog, je montrerai comment fonctionnent réellement des concepts avancés tels que la boucle d'événements en ce qui concerne le contexte d'exécution, la pile d'appels et la file d'attente de rappel.

Maîtrisons JavaScript en explorant son fonctionnement à partir de zéro

Photo de Marc St sur Unsplash

Avez-vous déjà rencontré des erreurs indéfinies ou eu du mal à identifier la portée d'une variable ?

C'est vraiment long de déboguer toute erreur sans savoir comment le code fonctionnait.

Dans ce blog, je vais vous montrer comment des concepts avancés tels event loopque execution context, call stacket callback queuefonctionnent réellement.

Un avertissement — Les concepts sont incroyablement riches en connaissances et interconnectés, alors ne clignez même pas des yeux !

Moteur JavaScript

Le moteur V8 de Google est une illustration bien connue d'un moteur JavaScript. Par exemple, Chrome et Node.js utilisent tous deux le moteur V8. Fondamentalement, le moteur V8 se compose de deux parties -

  1. Call Stack : Tout le code est exécuté à l'intérieur. Cela fonctionne comme la structure de données Stack, c'est-à-dire en suivant le concept LIFO (Last In First Out).
  2. Tas de mémoire : où l'allocation de mémoire se produit. Contrairement à la structure de données en tas, ce n'est qu'une mémoire.

Le contexte d'exécution global ou GEC est le contexte d'exécution par défaut créé par le moteur JavaScript chaque fois qu'un fichier de script est reçu.

Tout le code JavaScript qui n'est pas à l'intérieur d'une fonction est exécuté dans GEC. Les étapes suivantes sont effectuées dans GEC

  • Construire un espace mémoire pour stocker toutes les variables et fonctions à l'échelle mondiale
  • Générer un objet global.
  • Générer le mot-cléthis

En fonction de l'endroit où votre code sera exécuté, cela déterminera où thisse trouve. Par exemple, dans Node.js, il pointe vers un objet global distinct, alors que dans le navigateur, il pointe vers l' windowobjet.

Console du navigateur

Pile d'appels (ou pile d'exécution de fonctions)

JavaScript est single-threaded, comme vous l'avez déjà entendu. Mais qu'est-ce que cela signifie réellement ?

Cela signifie qu'un moteur JavaScript ne contient qu'un call stackou function execution stack.

  • Comme nous le savons, chaque fois que le compilateur explore votre code pour la première fois, le moteur JS est invité à créer un Global Execution Contextou GECpar le compilateur ainsi qu'à le placer dans le fichier Call Stack.
  • Votre code entier est exécuté un par un dans le Global Execution Contextet il alloue de la mémoire pour la définition de fonction ou la déclaration de variable et l'y stocke.
  • Mais lorsqu'un appel de fonction est trouvé, un Functional Execution Contextor FECest créé pour exécuter le code de la fonction, puis il est ajouté en haut du call stack.
  • L'interpréteur supprime une fonction de call stackchaque fois que la fonction se termine. Une fonction se termine - lorsqu'elle atteint la fin de sa portée ou une instruction de retour.
  • Enfin, l'exécution de tout votre code GECest supprimée du fichier Call Stack.

Ne vous inquiétez pas, démontrons-le avec un exemple.

function f1{
  console.log('f1');
}

f1();
console.log('end');

Étape 2 : — Dans notre exemple, la 1ère ligne sera exécutée, c'est-à-dire f1. Une mémoire sera allouée f1et stockée pour sa définition.

Étape 3 : — Dans la 2e ligne, une fonction est appelée. Pour cet appel de fonction, un Function Execution Context ou FECsera créé et sera stocké au-dessus du fichier Call Stack.

Étape 4 : — Maintenant, tout f1()sera exécuté ligne par ligne et après avoir terminé l'exécution, il sera supprimé du fichier Call Stack.

Étape 5 : — Ensuite, la dernière ligne console.log('end')sera exécutée et imprimera 'end' sur la console. Enfin, l'exécution de tout votre code Global Execution Contextsera supprimée du fichier Call Stack.

Comment JS gère-t-il les tâches asynchrones ?

JavaScript, comme nous sommes tous connus, est un langage synchrone à thread unique (une tâche à la fois), et le thread unique qui call stackexécute immédiatement tout ce qui se trouve à l'intérieur.

Mais que se passe-t-il si nous devons exécuter quelque chose après 5 secondes ? Pouvons-nous mettre cela à l'intérieur du call stack?

Non, nous ne pouvons pas. Parce call stackqu'il n'y a pas de minuterie. Mais comment pouvons-nous faire cela?

C'est là qu'intervient le runtime JavaScript .

Environnement d'exécution JavaScript

Cela peut être navré si je vous dis maintenant - setTimeout()ne fait pas partie de JavaScript, même si console.log()les événements DOM font tous partie des API Web qui donnent accès à JavaScript Engine pour utiliser toutes ses propriétés dans Global Execution Context (GEC) via l'objet global window. Ces API Web sont appelées asynchrones .

Qu'est-ce que la file d'attente de rappel ?

Une file d'attente de tâches appelée « file d'attente de rappel » ou « file d'attente de tâches » est une file d'attente qui est exécutée une fois que les tâches en cours de la pile d'appels sont terminées. Les tâches enregistrées dans l'API Web sont déplacées de l'API Web vers la file d'attente de rappel.

La file d'attente de rappel fonctionne comme une structure de données de file d'attente, ce qui signifie que les tâches sont gérées dans l'ordre FIFO (premier entré, premier sorti), par opposition à une pile d'appels, ce qui signifie que les tâches sont gérées dans l'ordre dans lequel elles ont été ajoutées à la file d'attente.

File d'attente de rappel

Qu'est-ce que la boucle d'événement ?

Une boucle d'événements JavaScript ajoute une tâche de la file d'attente de rappel à la pile des appels dans l'ordre FIFO dès que la pile des appels est vide.

La boucle d'événements est bloquée si la pile d'appels exécute actuellement du code et n'ajoutera pas d'appels supplémentaires à partir de la file d'attente tant que la pile ne sera pas à nouveau vide. Cela est dû au fait que le code JavaScript est exécuté de manière exécutée jusqu'à la fin.

Comprenons les concepts ci-dessus avec un exemple.

  • Au début, Global Execution Contextest créé pour notre code à l'intérieur de notre call stacket GECexécute notre code ligne par ligne.
  • GECexécute la 1ère ligne et imprime 'Start' sur notre console.
  • En exécutant la 2ème ligne, setTimeout()l'API Web sera appelée et setTimeout()donnera ensuite accès à la fonction de minuterie. Ensuite, vous pourrez définir 5000msun temps de retard.
  • Lorsque vous transmettez la callBack()fonction via le setTimeout(), le callBack()sera enregistré en tant que rappel sur l'API Web Web.
  • Et puis GECexécute la 1ère ligne et imprime 'End' sur la console.
  • Lorsque tout le code est exécuté, GECsera supprimé de notre fichier call stack.
  • Après 5000 millisecond, la callBack()fonction qui est enregistrée dans le web API, est déplacée à l'intérieur du call Back Queue.
  • Event loopmet la callBack()fonction dans le call Stackquand c'est fait c'est tout le travail. Et enfin, la callBack()fonction est exécutée et imprime 'Call Back' dans la console.

function f1() {
    console.log('f1');
}

function f2() {
    console.log('f2');
}

function main() {
    console.log('main');
    
    setTimeout(f1, 0);
    
    f2();
}

main();

Si vous pensez que "f1" sera imprimé avant "f2", alors vous vous trompez. Ce sera -

main
f2
f1

Le mécanisme discuté de JavaScript convient à toute fonction de rappel ou requête API.

Observons étape par étape comment le deuxième exemple fonctionne dans l'environnement d'exécution.

  1. Au début GECsera créé à l'intérieur du call stackpuis le code sera exécuté ligne par ligne dans GEC. Il stocke toutes les définitions de fonctions dans le tas de mémoire .
  2. Lorsque le main()est appelé, un Function Execution Context (FEC)est créé, puis il entre dans la pile des appels. Après cela, tout le code de la main()fonction sera exécuté ligne par ligne.
  3. Il a un journal de console pour imprimer le mot main. Ainsi, le console.log('main')s'exécute et sort de la pile.
  4. L' setTimeout()API du navigateur prend place. La fonction de rappel le place dans la file d'attente de rappel. Mais dans la pile, l'exécution se produit comme d'habitude, donc f2()entre dans la pile. Le journal de la console des f2()exécutions. Les deux sortent de la pile.
  5. Et puis le main()sort également de la pile.
  6. La boucle d'événements reconnaît que la pile d'appels est vide et qu'il existe une fonction de rappel dans la file d'attente. Ainsi, la fonction de rappel f1()sera placée dans la pile par le event loop. L'exécution commence. Le journal de la console s'exécute et f1()sort également de la pile. Et enfin, rien d'autre n'est dans la pile et la file d'attente pour s'exécuter davantage.

C'est ma pratique et ma note. Si vous l'avez trouvé utile, veuillez montrer votre soutien en cliquant sur l'icône clap ci-dessous. Vous pouvez me suivre sur medium .