Maison >interface Web >js tutoriel >Démystifier la pile d'appels JavaScript : comment votre code s'exécute réellement
Le fonctionnement de la pile d'appels JavaScript est quelque chose que chaque développeur frontend s'est posé au moins une fois dans sa carrière, et à mon avis, ce n'est pas une question qui s'est posée ont reçu des réponses dans la plupart des endroits, et les réponses ne sont pas toujours claires ou faciles à comprendre. C'est pourquoi j'ai décidé de couvrir le sujet dans cet article.
Commençons par le début. Le moteur JavaScript exécute le code ligne par ligne de manière synchrone, et chaque fois qu'une fonction est exécutée, il crée un contexte d'exécution (un espace en mémoire pour stocker toutes les propriétés étendues qui n'existent que à l'intérieur de cette fonction) et ajoute la fonction à la pile d'appels.
JavaScript exécute uniquement le code de la fonction qui se trouve en haut de la pile, et lorsqu'une fonction finalise et renvoie sa valeur, le moteur supprime la fonction de la pile d'appels et commence à travailler sur le suivant.
Lorsque la pile d'appels est vide, le moteur JavaScript continue d'exécuter le code dans le contexte global suivant, ou ce qui revient au même, continue d'exécuter les lignes qui se trouvent dans la racine du fichier JavaScript et n'appartiennent à aucune fonction.
Voyons quelques exemples, ligne par ligne :
const num1 = 2; const num2 = 5; function sum(a, b){ return a + b; } const result= sum(num1, num2); console.log(result) // 7
Il s'agit d'un code très simple qui définit 2 constantes (num1 et num2) puis définit une fonction sum qui additionne 2 nombres et renvoie le résultat de la somme. Enfin, la constante result est créée, et le résultat de l'exécution de sum avec les arguments num1 et num2 lui est attribué. Ensuite la valeur du résultat est imprimée sur la console.
Si vous pensez que l'explication précédente est trop simple ou trop complexe et n'explique rien, veuillez être patient avec moi, nous arrivons aux choses intéressantes.
Voyons ce que fait le moteur JavaScript, ligne par ligne. Dans la première ligne, le moteur crée un label num1 et stocke en mémoire la valeur 2.
const num1 = 2;
La deuxième ligne fait de même pour l'étiquette num2. Il crée un label num2 et stocke en mémoire la valeur 5.
const num2 = 5;
Ce que cela signifie, c'est que chaque fois que quelqu'un à l'intérieur de la portée globale requiert la valeur de num2 le moteur va changer l'étiquette et mettre la valeur 5 à la place.
Continuons avec la ligne suivante. La ligne suivante est la fonction somme. À votre avis, que va faire le moteur ? Pensez-vous qu'il va exécuter la fonction ou l'ajouter à la pile d'appels ? Non ! Ce que le moteur va faire, c'est stocker une nouvelle étiquette nommée somme et stocker le code entre parenthèses en mémoire. Ou ce qui revient au même, il va stocker la définition de la fonction et l'attribuer à l'étiquette somme.
function sum(a, b){ return a + b; }
si nous pouvions voir visuellement la mémoire du code que nous avons exécuté jusqu'à présent, nous verrions quelque chose comme ceci :
Label name | Value in memory |
---|---|
num1 | 2 |
num2 | 5 |
sum | function definition |
La ligne suivante est vraiment intéressante. Lorsque le moteur JavaScript atteint la ligne suivante, il crée le label résultat, mais à ce stade, il ne sait pas encore quelle valeur doit être attribuée au label car la valeur est le résultat de l'exécution d'une fonction, donc d'abord, elle doit exécuter la fonction, faire tout ce que la fonction doit faire et obtenir le résultat de la valeur de retour.
const result= sum(num1, num2);
À ce stade, le moteur ajoute la fonction à la pile d'appels et crée également un nouveau contexte d'exécution, qui est un nouvel espace en mémoire où JavaScript peut stocker la valeur des arguments et de toutes les autres propriétés contenues dans la fonction sans entrer en collision avec le contexte global.
Call stack |
---|
sum |
Global |
First of all, the engine creates in memory the labels a and b which are the names given to the parameters, and it assigns the value of the arguments 2 and 5 respectively.
If we could see the memory at this specific moment, we would see something like this:
Label name | Value in memory |
---|---|
a | 2 |
b | 5 |
return | 2 + 5 = 7 |
In this case, the function is really simple and only returns the value of the sum between a and b, so the engine substitutes the parameters with the values of the arguments and returns the value to the global execution context. Finally, the function is removed from the call stack, and only the global context remains.
Call stack |
---|
Global |
At this point, the result of the function is assigned to the label result and we can print the value on console with the console log.
Let's take a look at how the global memory looks now:
Label name | Value in memory |
---|---|
num1 | 2 |
num2 | 5 |
sum | function definition |
result | 7 |
Did you noticed? the label result has a value of 7? and also sum still has the function definition inside.
Let's take a look at the next code:
const num1 = 2; const num2 = 5; function sum(a, b){ return a + b; } const result= sum(num1, num2); console.log(result) // 7 function sumThreeNumbers = (x,y,z) => { return sum(x, y) + z } const result2 = sumThreeNumbers(4,6,2) console.log(result2) // 12
The main difference is that now we have a new sumThreeNumbers function and we are creating a new result2 constant and assigning the value of running the function sumThreeNumbers with the arguments, 4, 6 and 2.
Let's take a look at how the call stack works when we run nested functions.
If we jump to the line when we define the constant result2 the global memory would look something like this:
Label name | Value in memory |
---|---|
num1 | 2 |
num2 | 5 |
sum | function definition |
result | 7 |
sumThreeNumbers | function definition |
Just as on the previous example, the JavaScript engine doesn't know what value to assign to the label result2, to get the value, first needs to execute the function sumThreeNumbers with the arguments. The function is added to the call stack, and a new execution context is created. The execution context would look like this:
Label name | Value in memory |
---|---|
x | 4 |
y | 6 |
z | 2 |
So the first thing that JavaScript does is create the parameter labels and assign the value provided by the arguments.
Now let's take a look at our call stack
Call stack |
---|
sumThreeNumbers |
Global |
As you can see, the call stack only has the sumThreeNumbers item (apart from the global context that is always present).
To be able to get the result value, the function sum needs to be executed first, so the engine will add the function to the call stack and create a new execution context for the sum function.
Call stack |
---|
sum |
sumThreeNumbers |
Global |
As the sum function is on top of the call stack, Javascript needs to run sum first to be able to continue running sumThreeNumbers.
This is how it's going to look the execution context for the function sum:
Label name | Value in memory |
---|---|
a | 4 |
b | 6 |
return | 4 + 6 = 10 |
Comme vous le savez, il renverra _10 _et il sera supprimé de la pile d'appels
Call stack |
---|
sumThreeNumbers |
Global |
Le JavaScript va continuer à exécuter le sumThreeNumbers et le contexte d'exécution ressemblera à ceci :
Label name | Value in memory |
---|---|
x | 4 |
y | 6 |
z | 2 |
return | 10 + 2 = 12 |
Il renverra la valeur 12 et sera supprimé de la pile d'appels.
Call stack |
---|
Global |
Ensuite la valeur 12 sera attribuée à la propriété result2 et la valeur 12 sera affichée dans la console.
J'espère que cet article vous a aidé à comprendre comment fonctionne la pile d'appels JavaScript, si c'est le cas, laissez un commentaire et un like. Je te vois dans le prochain !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!