Comment démarrer rapidement avec VUE3.0 : découvrez
le contexte d'exécution, la pile d'exécution et le mécanisme d'exécution (tâches synchrones, tâches asynchrones, microtâches, macrotâches et boucles d'événements) dans js
Il s'agit d'un point de test fréquent dans les entretiens et. certains amis le sont Vous pouvez vous sentir confus lorsqu'on vous le demande, je vais donc le résumer aujourd'hui, en espérant que cela puisse vous être utile devant l'écran.
Avant de parler du contexte d'exécution et du mécanisme d'exécution js
dans js
, parlons des threads et des processus.
En termes officiels,线程
est la plus petite unité de planification CPU
.
? En termes officiels,进程
est la plus petite unité d'allocation de ressources CPU
.
线程
est une unité d'exécution de programme basée sur进程
.En termes simples,线程
est un flux d'exécution dans un programme.Un进程
peut avoir un ou plusieurs线程
.
Il n'y a qu'un seul flux d'exécution dans un进程
appelé单线程
.C'est - à - dire que lorsque le programme est exécuté, les chemins du programme empruntés sont classés dans un ordre consécutif. Les précédents doivent être traités avant que les suivants ne soient exécutés.
Plusieurs flux d'exécution dans un进程
sont appelés多线程
, c'est-à-dire que plusieurs线程
différents peuvent être exécutés simultanément dans un programme pour effectuer différentes tâches, ce qui signifie qu'un seul programme est autorisé à créer plusieurs线程
d'exécution parallèles pour accomplir leurs tâches respectives. .
L'auteur donnera un exemple simple ci-dessous. Par exemple, si nous ouvrons qq音乐
et écoutons des chansons, la qq音乐
qq音乐
peut être comprise comme un processus, nous pouvons télécharger tout en écoutant des chansons. les chansons sont un fil conducteur, et le téléchargement est un processus. Si nous ouvrons à nouveau vscode
pour écrire du code, ce sera un autre processus.
Les processus sont indépendants les uns des autres, mais certaines ressources sont partagées entre les threads du même processus.
Le cycle de vie d'un thread passe par cinq étapes.
Nouvel état : après avoir utilisé le mot-clé new
et Thread
ou sa sous-classe pour créer un objet thread, l'objet thread est dans le nouvel état. Il reste dans cet état jusqu'à ce que le programme start()
le thread.
État prêt : lorsque l'objet thread appelle start()
, le thread entre dans l'état prêt. Le thread à l'état prêt est dans la file d'attente prêt et peut être exécuté immédiatement tant qu'il obtient le droit d'utiliser CPU
.
État d'exécution : si le thread à l'état prêt obtient des ressources CPU
, il peut exécuter run()
et le thread est à l'état d'exécution. Le thread en cours d'exécution est le plus complexe, il peut devenir bloqué, prêt et mort.
État de blocage : si un thread exécute sleep(睡眠)
, suspend(挂起)
, wait(等待)
et d'autres méthodes, après avoir perdu les ressources occupées, le thread entrera dans l'état de blocage à partir de l'état d'exécution. L'état prêt peut être rétabli une fois le temps de veille expiré ou après l'obtention des ressources de l'appareil. Il peut être divisé en trois types :
blocage en attente : le thread en état d'exécution exécute wait()
, faisant entrer le thread dans l'état de blocage en attente.
Blocage synchrone : le thread ne parvient pas à acquérir le verrou de synchronisation synchronized
(car le verrou de synchronisation est occupé par d'autres threads).
Autre blocage : lorsqu'une requête I/O
est émise en appelant le sleep()
ou join()
du thread, le thread entrera dans l'état de blocage. Lorsque sleep()
expire, join()
attend que le thread se termine ou expire, ou que le traitement I/O
soit terminé, et le thread revient à l'état prêt.
État de mort : lorsqu'un thread en cours d'exécution termine sa tâche ou que d'autres conditions d'arrêt se produisent, le thread passe à l'état terminé.
JS
En tant que langage de script de navigateur, JS
est principalement utilisé pour interagir avec les utilisateurs et faire fonctionner DOM
. Cela détermine qu'il ne peut être qu'un seul thread, sinon cela entraînera des problèmes de synchronisation très complexes. Par exemple, supposons que JavaScript
ait deux threads en même temps. Un thread ajoute du contenu à un certain nœud DOM
et l'autre thread supprime le nœud. Dans ce cas, quel thread le navigateur doit-il utiliser ?
Lorsque JS
analyse un fragment de code exécutable (généralement la phase d'appel de fonction), il effectue d'abord un travail préparatoire avant l'exécution. Ce « travail de préparation » est appelé « contexte d'exécution ». (Contexte d'exécution (appelé EC
)" ou il peut également être appelé environnement d'exécution .
Il existe trois types de contexte d'exécution en javascript
:
contexte d'exécution global Il s'agit du contexte d'exécution par défaut ou le plus basique. Il n'y aura qu'un seul contexte global dans un programme, et il existera tout au long du cycle de vie du programme. Script javascript
. Le bas de la pile d'exécution ne sera pas détruit par le stack popping. Le contexte global générera un objet global (en prenant l'environnement du navigateur comme exemple, cet objet global est window
) et liera la valeur this
à cet objet global.
Contexte d'exécution de fonction Chaque fois qu'une fonction est appelée, un nouveau contexte d'exécution de fonction est créé (que la fonction soit appelée à plusieurs reprises ou non).
Contexte d'exécution de la fonction Eval Le code exécuté à l'intérieur eval
aura également son propre contexte d'exécution, mais comme eval
n'est pas souvent utilisé, il ne sera pas analysé ici.
Plus tôt, nous avons mentionné que js
créera un contexte d'exécution lors de son exécution, mais le contexte d'exécution doit être stocké, alors qu'est-ce qui est utilisé pour le stocker ? Vous devez utiliser la structure de données de la pile.
La pile est une structure de données premier entré, dernier sorti.
Donc, en résumé, le contexte d'exécution utilisé pour stocker le contexte d'exécution créé lors de l'exécution du code est la pile d'exécution .
Lors de l'exécution d'un morceau de code JS
Ensuite, JS
créera un contexte d'exécution global et push
vers la pile d'exécution. Dans ce processus, JS
allouera de la mémoire pour toutes les variables de ce code et attribuera une valeur initiale (non définie). Le moteur JS
entrera dans la phase d'exécution, dans ce processus, JS
exécutera le code ligne par ligne, c'est-à-dire attribuera des valeurs (valeurs réelles) aux variables qui ont reçu de la mémoire une par une.
S'il y a function
dans ce code, JS
créera un contexte d'exécution de fonction et push
vers la pile d'exécution. Le processus de création et d'exécution est le même que le contexte d'exécution global.
Lorsqu'une pile d'exécution est terminée, le contexte d'exécution sera extrait de la pile, puis le contexte d'exécution suivant sera entré.
Permettez-moi de donner un exemple ci-dessous si nous avons le code suivant dans notre programme :
console.log("Global Execution Context start"); fonction d'abord() { console.log("première fonction"); deuxième(); console.log("Encore une première fonction"); } fonction seconde() { console.log("deuxième fonction"); } d'abord(); console.log("Global Execution Context end");
Analysons brièvement l'exemple ci-dessus.
Tout d'abord, une pile d'exécution sera créée
, puis un contexte global sera créé et le contexte d'exécution push
vers la pile d'exécution
pour démarrer l'exécution. , et Global Execution Context start
rencontre first
méthode, exécute la méthode, crée un contexte d'exécution de fonction et push
vers la pile d'exécution
pour exécuter first
contexte d'exécution, génère first function
, rencontre second
méthode, exécute la méthode. , crée un contexte d'exécution de fonction et push
vers la pile d'exécution pour
exécuter second
contexte d'exécution, la second function
second
sortie a été exécutée, extraite de la pile et entrée dans le contexte de first
exécution,
le contexte first
exécution continue
exécution, sortie Again first function
first
été exécutée, extraite de la pile et entrée dans le contexte d'exécution suivant Contexte d'exécution global Contexte
d'exécution global Continuer l'exécution et sortir Global Execution Context end
Nous utilisons une image pour résumer.
Très bien. Après avoir parlé du contexte d'exécution et de la pile d'exécution, parlons du mécanisme d'exécution de js . En parlant du mécanisme d'exécution de js
js
devons comprendre les tâches synchrones, les tâches asynchrones, les macro-tâches et les micro-tâches dans js
.
Dans js
, les tâches sont divisées en tâches synchrones et tâches asynchrones. Alors, que sont les tâches synchrones et que sont les tâches asynchrones ?
Les tâches synchrones font référence aux tâches mises en file d'attente pour exécution sur le thread principal. La tâche suivante ne peut être exécutée qu'après l'exécution de la tâche précédente.
Les tâches asynchrones font référence aux tâches qui n'entrent pas dans le thread principal mais entrent dans la « file d'attente des tâches » (les tâches de la file d'attente des tâches sont exécutées en parallèle avec le thread principal uniquement lorsque le thread principal est inactif et que la « file d'attente des tâches » en informe le). thread principal, une tâche asynchrone Une fois qu'elle peut être exécutée, la tâche entrera dans le thread principal pour être exécutée. Puisqu'il s'agit d'un stockage en file d'attente, il répond à la règle du premier entré, premier sorti . Les tâches asynchrones courantes incluent nos setInterval
, setTimeout
, promise.then
, etc.
a précédemment introduit les tâches synchrones et les tâches asynchrones. Parlons maintenant de la boucle d'événements.
Les tâches synchrones et asynchrones entrent respectivement dans différents « lieux » d'exécution et entrent dans le thread principal de manière synchrone. Ce n'est que lorsque la tâche précédente est terminée que la tâche suivante peut être exécutée. Les tâches asynchrones n'entrent pas dans le thread principal mais entrent dans Event Table
et dans les fonctions d'enregistrement.
Lorsque l'opération spécifiée est terminée, Event Table
déplacera cette fonction dans Event Queue
. Event Queue
est une structure de données de file d'attente, elle répond donc à la règle du premier entré, premier sorti.
Lorsque les tâches du thread principal sont vides après exécution, la fonction correspondante sera lue dans Event Queue
et exécutée dans le thread principal.
Le processus ci-dessus sera répété en continu, souvent appelé Event Loop .
Résumons-le avec une image
Permettez-moi de présenter brièvement un exemple
de fonction test1() { console.log("log1"); setTimeout(() => { console.log("setTimeout 1000"); }, 1000); setTimeout(() => { console.log("setTimeout 100"); }, 100); console.log("log2"); } test1(); // log1, log2, setTimeout 100, setTimeout 1000
Nous savons que dans js, les tâches synchrones seront exécutées avant les tâches asynchrones, donc l'exemple ci-dessus affichera d'abord log1、log2
,
puis exécutera les tâches asynchrones après les tâches synchrones.
rappel
100
setTimeout 100
délai de 1000
millisecondes exécutera la sortie setTimeout 1000
plus tard.
tant que vous comprenez les tâches synchrones et asynchrones mentionnées par l'auteur ci-dessus, il n'y aura aucun problème. Alors laissez-moi vous donner un autre exemple, mes amis, voyons quel sera le résultat.
fonction test2() { console.log("log1"); setTimeout(() => { console.log("setTimeout 1000"); }, 1000); setTimeout(() => { console.log("setTimeout 100"); }, 100); nouvelle promesse ((résoudre, rejeter) => { console.log("nouvelle promesse"); résoudre(); }).then(() => { console.log("promise.then"); }); console.log("log2"); } test2();
Pour résoudre le problème ci-dessus, il ne suffit pas de connaître les tâches synchrones et asynchrones. Nous devons également connaître les macro-tâches et les micro-tâches.
Dans js
, les tâches sont divisées en deux types, l'une est appelée macro-tâche MacroTask
et l'autre est appelée micro-tâche MicroTask
.
La macro-tâche commune MacroTask
a
le bloc de code principal
setTimeout()
setInterval()
setImmediate() - Node
requestAnimationFrame() - le navigateur.
La micro-tâche commune MicroTask
a
Promise.then()
process.nextTick() - Node
. exemple ci-dessus Il s'agit de macro-tâches et de micro-tâches. Quel est l'ordre d'exécution des macro-tâches et des micro-tâches ?
Tout d'abord, lorsque le script
global (en tant que première tâche macro) commence à être exécuté, tout le code sera divisé en deux parties : les tâches synchrones et les tâches asynchrones. Les tâches synchrones entreront directement dans le thread principal pour être exécutées en séquence, et les tâches asynchrones entreront dans la file d'attente asynchrone, puis divisées en macro-tâches et micro-tâches.
La macro-tâche entre dans Event Table
et y enregistre une fonction de rappel. Chaque fois que l'événement spécifié est terminé, Event Table
déplace cette Event Queue
vers la file d'attente d'événements. La micro-tâche entre également dans une autre Event Table
et s'y enregistre. . Chaque fois que l'événement spécifié est terminé, Event Table
déplacera cette fonction vers Event Queue
Lorsque les tâches du thread principal sont terminées et que le thread principal est vide, Event Queue
de la microtâche sera vérifiée s'il y a des tâches. , tous Exécutez, sinon, exécutez la tâche macro suivante
Nous utilisons une image pour la résumer.
Après avoir compris les exemples ci-dessus de macro-tâches et de micro-tâches asynchrones, nous pouvons facilement obtenir la réponse.
Nous savons que dans js, les tâches synchrones seront exécutées avant les tâches asynchrones, donc l'exemple ci-dessus affichera en premier log1、new promise、log2
. Il convient de noter ici que le bloc de code principal de la nouvelle promesse est synchronisé.Une
une fois toutes les micro-tâches exécutées
promise.then
, une autre tâche de macro sera exécutée, retardant La fonction de rappel de 100
millisecondes donnera la priorité à l'exécution et affichera setTimeout 100
Cette macrotâche ne génère pas de microtâches, il n'y a donc aucune microtâche à exécuter
pour continuer à exécuter la macrotâche suivante Le rappel. La fonction avec un délai de 1000
donnera la priorité à l'exécution et affichera setTimeout 1000
donc une fois la méthode test2 exécutée, elle affichera log1、new promise、log2、promise.then、setTimeout 100、setTimeout 1000
dans l'ordre
. différentes opinions sur l'opportunité d'exécuter
js
d'abord avec des tâches macro, puis des micro-tâches ou avec des micro-tâches avant les macro-tâches. La compréhension de l'auteur est que si l'ensemble du bloc de codejs
est considéré comme une macro-tâche, notre ordre d'exécutionjs
est d'abord une macro-tâche, puis une micro-tâche.
Comme le dit le proverbe, il vaut mieux s'entraîner une fois que regarder cent fois. Je vais vous donner deux exemples ci-dessous. Si vous pouvez le faire correctement, alors vous maîtrisez la connaissance du mécanisme d'exécution js
.
Exemple 1,
fonction test3() { console.log(1); setTimeout (fonction () { console.log(2); nouvelle promesse (fonction (résoudre) { console.log(3); résoudre(); }).then(fonction () { console.log(4); }); console.log(5); }, 1000); nouvelle promesse (fonction (résoudre) { console.log(6); résoudre(); }).then(fonction () { console.log(7); setTimeout(fonction () { console.log(8); }); }); setTimeout (fonction () { console.log(9); nouvelle promesse (fonction (résoudre) { console.log(10); résoudre(); }).then(fonction () { console.log(11); }); }, 100); console.log(12); } test3();
Analysons-le en détail.
Tout d'abord, le bloc de code js
global est exécuté comme une tâche macro, et 1, 1、6、12
sont affichés dans l'ordre.
Une fois la macro-tâche globale du bloc de code exécutée, une micro-tâche et deux macro-tâches sont générées, de sorte que la file d'attente des macro-tâches comporte deux macro-tâches et la file d'attente des micro-tâches comporte une micro-tâche.
Une fois la macro-tâche exécutée, toutes les micro-tâches générées par cette macro-tâche seront exécutées. Comme il n’y a qu’une seule microtâche, 7
seront générées. Cette microtâche a généré une autre macrotâche, il y a donc actuellement trois macrotâches dans la file d'attente des macrotâches.
Parmi les trois macrotâches, celle sans délai défini est exécutée en premier, donc 8
est généré. Cette macrotâche ne génère pas de microtâches, il n'y a donc pas de microtâches à exécuter et la macrotâche suivante continue d'être exécutée.
Retardez l'exécution de la macrotâche de 100
millisecondes, sortez 9、10
et générez une microtâche, de sorte que la file d'attente des microtâches ait actuellement une microtâche.
Une fois la macrotâche exécutée, toutes les microtâches générées par la macrotâche seront exécutées, donc la microtâche sera exécutée. exécutées. Toutes les microtâches de la file d'attente des tâches génèrent 11
attente
2、3、5
1000
contient actuellement une microtâche.
la macrotâche sera exécutée.Toutes les microtâches sont générées, donc toutes les microtâches de la file d'attente des microtâches seront exécutées et 4
sera affiché
. Ainsi, l'exemple de code ci-dessus affichera 1、6、12、7、8、9、10、11、2、3、5、4
, 12, 7, 8, 9, 10, 11. , 2, 3, 5, 4 dans l'ordre. Vous l'avez bien fait ?
Exemple 2 :
Nous modifions légèrement l'exemple 1 ci-dessus et introduisons la fonction async
et await
async test4() { console.log(1); setTimeout (fonction () { console.log(2); nouvelle promesse (fonction (résoudre) { console.log(3); résoudre(); }).then(fonction () { console.log(4); }); console.log(5); }, 1000); nouvelle promesse (fonction (résoudre) { console.log(6); résoudre(); }).then(fonction () { console.log(7); setTimeout(fonction () { console.log(8); }); }); résultat const = attendre async1(); console.log(résultat); setTimeout (fonction () { console.log(9); nouvelle promesse (fonction (résoudre) { console.log(10); résoudre(); }).then(fonction () { console.log(11); }); }, 100); console.log(12); } fonction asynchrone async1() { console.log(13) return Promise.resolve("Promise.resolve"); } test4();
Que produira l'exemple ci-dessus ? Ici, nous pouvons facilement résoudre le problème de async
et await
.
Nous savons async
et await
sont en fait du sucre de syntaxe pour Promise
. Ici, il nous suffit de savoir await
est équivalent à Promise.then
. Nous pouvons donc comprendre l'exemple ci-dessus comme le code suivant
function test4() { console.log(1); setTimeout (fonction () { console.log(2); nouvelle promesse (fonction (résoudre) { console.log(3); résoudre(); }).then(fonction () { console.log(4); }); console.log(5); }, 1000); nouvelle promesse (fonction (résoudre) { console.log(6); résoudre(); }).then(fonction () { console.log(7); setTimeout(fonction () { console.log(8); }); }); nouvelle promesse (fonction (résoudre) { console.log(13); return solve("Promise.resolve"); }).then((résultat) => { console.log(résultat); setTimeout (fonction () { console.log(9); nouvelle promesse (fonction (résoudre) { console.log(10); résoudre(); }).then(fonction () { console.log(11); }); }, 100); console.log(12); }); } test4();
Pouvez-vous facilement obtenir le résultat après avoir vu le code ci-dessus ?
Premièrement, l'intégralité du bloc de code js
est initialement exécutée en tant que macro-tâche et génère 1、6、13
dans l'ordre.
Une fois la macro-tâche globale du bloc de code exécutée, deux micro-tâches et une macro-tâche sont générées, de sorte que la file d'attente des macro-tâches comporte une macro-tâche et la file d'attente des micro-tâches comporte deux micro-tâches.
Une fois la macro-tâche exécutée, toutes les micro-tâches générées par cette macro-tâche seront exécutées. Donc 7、Promise.resolve、12
seront affichés. Cette microtâche a généré deux macrotâches supplémentaires, donc la file d'attente des macrotâches contient actuellement trois macrotâches.
Parmi les trois macrotâches, celle sans délai défini est exécutée en premier, donc 8
est généré. Cette macrotâche ne génère pas de microtâches, il n'y a donc pas de microtâches à exécuter et la macrotâche suivante continue d'être exécutée.
Retardez l'exécution de la macrotâche de 100
millisecondes, sortez 9、10
et générez une microtâche, de sorte que la file d'attente des microtâches ait actuellement une microtâche.
Une fois la macrotâche exécutée, toutes les microtâches générées par la macrotâche seront exécutées, donc la microtâche sera exécutée. exécutées. Toutes les microtâches de la file d'attente des tâches génèrent 11
attente
2、3、5
1000
contient actuellement une microtâche.
la macrotâche sera exécutée. Toutes les microtâches générées exécuteront toutes les microtâches de la file d'attente des microtâches et afficheront 4
Par conséquent, l'exemple de code ci-dessus affichera 1, 6, 13, 7 1、6、13、7、Promise.resolve、12、8、9、10、11、2、3、5、4
4, vous l'avez bien fait ?
De nombreux amis ne comprennent peut-être toujours pas setTimeout(fn)
.N'est-il pas évident que le délai n'est pas défini?Ne devrait-il pas être exécuté immédiatement ?
Nous pouvons comprendre setTimeout(fn)
comme setTimeout(fn,0)
, ce qui signifie en fait la même chose.
Nous savons que js est divisé en tâches synchrones et tâches asynchrones. setTimeout(fn)
est une tâche asynchrone, donc même si vous ne définissez pas de délai ici, elle entrera dans la file d'attente asynchrone et ne sera exécutée que lorsque le thread principal sera terminé. inactif.
L'auteur le mentionnera à nouveau, pensez-vous que le temps de retard que nous avons défini après setTimeout
, js
sera définitivement exécuté en fonction de notre temps de retard, je ne pense pas. L'heure que nous définissons est uniquement celle pendant laquelle la fonction de rappel peut être exécutée, mais la question de savoir si le thread principal est libre est une autre affaire. Nous pouvons donner un exemple simple.
fonction test5() { setTimeout (fonction () { console.log("setTimeout"); }, 100); soit je = 0 ; tandis que (vrai) { je++; } } test5();
L'exemple ci-dessus affichera-t-il définitivement setTimeout
après 100
millisecondes ? Non, car notre thread principal est entré dans une boucle infinie et n'a pas le temps d'exécuter des tâches de file d'attente asynchrones.
GUI渲染
est mentionné ici. Certains amis peuvent ne pas le comprendre. Je le présenterai en détail plus tard dans un article sur les navigateurs.
Étant donné que JS引擎线程
et GUI渲染线程
s'excluent mutuellement, afin de permettre宏任务
et DOM任务
de se dérouler de manière ordonnée, le navigateur démarrera GUI渲染线程
après le résultat de l'exécution d'une宏任务
et avant le exécution de la prochaine宏任务
, rendre la page.
Par conséquent, la relation entre les macro-tâches, les micro-tâches et le rendu GUI est la suivante :
macro-tâche -> micro-tâche -> GUI rendu -> macro-tâche ->...
[Recommandation de didacticiel vidéo associée : interface Web]
Ce qui précède est une analyse approfondie de JavaScript Pour plus de détails sur le contexte d'exécution et le mécanisme d'exécution, veuillez prêter attention aux autres articles connexes sur le site Web PHP chinois pour plus d'informations !