Cet article vous apporte quelques problèmes liés au mécanisme d'exécution en JavaScript. Qu'il s'agisse de travail ou d'un entretien, nous pouvons souvent rencontrer des scénarios où nous avons besoin de connaître l'ordre d'exécution du code. J'espère que cela sera utile à tout le monde.
Processus et fils de discussion
Nous savons tous que le cœur de l'ordinateur est le processeur, qui entreprend toutes les tâches informatiques et que le système d'exploitation est le gestionnaire de l'ordinateur, qui est responsable de la tâche ; la planification, l'allocation et la gestion des ressources, régissant l'ensemble du matériel informatique ; les programmes d'application sont des programmes dotés de certaines fonctions et des programmes exécutés sur le système d'exploitation.
Process
Un processus est un processus d'exécution dynamique d'un programme avec des fonctions indépendantes sur un ensemble de données. Il s'agit d'une unité indépendante d'allocation et de planification des ressources par le système d'exploitation, et constitue le processus porteur pour l'application. en cours d'exécution. Il s'agit de la plus petite unité capable de posséder des ressources et de fonctionner de manière indépendante, et c'est également la plus petite unité pour l'exécution du programme.
Caractéristiques d'un processus :
Dynamique : Un processus est un processus d'exécution d'un programme. Il est temporaire, a un cycle de vie, est généré dynamiquement et meurt
Concurrence : tout processus peut être ; exécuté simultanément avec d'autres processus ;
Indépendance : le processus est une unité indépendante du système pour l'allocation des ressources et la planification ;
Structural : le processus se compose de trois parties : le programme, les données et le bloc de contrôle du processus.
Thread
Un thread est un processus de contrôle séquentiel unique dans l'exécution d'un programme. Il s'agit de la plus petite unité du flux d'exécution du programme et de l'unité de base de la planification et de la répartition du processeur. Un processus peut avoir un ou plusieurs threads, et chaque thread partage l'espace mémoire du programme (c'est-à-dire l'espace mémoire du processus). Un thread standard se compose d'un ID de thread, d'un pointeur d'instruction actuel (PC), de registres et d'une pile. Le processus se compose d'un espace mémoire (code, données, espace de processus, fichiers ouverts) et d'un ou plusieurs threads.
La différence entre le processus et le thread
Le thread est la plus petite unité d'exécution d'un programme, et le processus est la plus petite unité d'allocation de ressources par le système d'exploitation
Un processus se compose d'un ou plusieurs threads et d'un le thread est le code d'un processus Différentes routes d'exécution ;
Les processus sont indépendants les uns des autres, mais chaque thread sous le même processus partage l'espace mémoire du programme (y compris les segments de code, les ensembles de données, les tas, etc.) et certains processus -au niveau des ressources (telles que l'ouverture de fichiers et de signaux), les processus sont invisibles les uns aux autres ;
Planification et commutation : le changement de contexte de thread est beaucoup plus rapide que le changement de contexte de processus.
Pourquoi JS est-il monothread ?
JavaScript est utilisé comme langage de script de navigateur depuis sa naissance. Il est principalement utilisé pour gérer les interactions des utilisateurs et faire fonctionner le 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 : si JS est multithread, un thread veut modifier un élément DOM et un autre thread veut supprimer l'élément DOM, alors le navigateur ne sait pas qui écouter. Ainsi, afin d'éviter toute complexité, JavaScript a été conçu pour être monothread depuis sa naissance.
Afin de profiter de la puissance de calcul des CPU multicœurs, HTML5 propose le standard Web Worker, qui permet aux scripts JavaScript de créer plusieurs threads, mais les threads enfants sont entièrement contrôlés par le thread principal et ne doivent pas faire fonctionner le DOM. . Par conséquent, cette nouvelle norme ne change pas la nature monothread de JavaScript
Principe du navigateur
En tant qu'ingénieur front-end, vous devez être familier avec les navigateurs, et les navigateurs sont multi-processus.
Composants du navigateur
Interface utilisateur : y compris barre d'adresse, avant/arrière/actualisation/signets
Moteur de navigateur : transfère les instructions entre l'interface utilisateur et le moteur de rendu
Moteur de rendu : utilisé pour dessiner le contenu demandé
Réseau : utilisé pour effectuer des appels réseau, tels que des requêtes http, il possède une interface indépendante de la plate-forme et peut fonctionner sur différentes plates-formes
Interpréteur JavaScript : utilisé pour analyser et exécuter du code JavaScript
Backend de l'interface utilisateur : utilisé pour dessiner des widgets de base, tels que des zones de liste déroulante et des fenêtres. La couche inférieure utilise l'interface utilisateur du système d'exploitation
Stockage des données : elle appartient à la couche de persistance et le navigateur enregistre. sur le disque dur Pour diverses données similaires aux cookies, HTML5 définit la technologie de base de données Web, qui est une technologie de stockage légère et complète côté client
Remarque : contrairement à la plupart des navigateurs, chaque navigateur de Google (Chrome) Chaque page d'onglet correspond à une instance de moteur de rendu. Chaque onglet est un processus indépendant
Quels processus le navigateur contient-il
Processus du navigateur
Le processus principal du navigateur (responsable de la coordination et du contrôle), il n'y a qu'un seul processus
Responsable de l'affichage de l'interface du navigateur et de l'interaction avec les utilisateurs. Tels que l'avant, l'arrière, etc.
Responsable de la gestion de chaque page, créant et détruisant d'autres processus
Dessinez le Bitmap (bitmap) dans la mémoire obtenue par le processus de rendu (Renderer) vers l'interface utilisateur
Gestion des ressources réseau, des téléchargements, etc. accélération matérielle (au plus une)
Responsable de l'analyse, de l'exécution et du rendu des documents de page
Quels threads sont inclus dans le processus de rendu
Fil de rendu GUIPrincipalement responsable de analyse HTML, CSS, création d'une arborescence DOM, mise en page, dessin, etc.
Ce fil est lié au moteur JavaScript. Les threads s'excluent mutuellement Lorsque le thread du moteur JavaScript est exécuté, le thread de rendu de l'interface graphique sera suspendu lorsque la file d'attente des tâches. est inactif, le thread principal exécutera le rendu GUI
Le thread du moteur JavaScript est principalement responsable du traitement des scripts JavaScript et de l'exécution du code (comme le moteur V8)
Le navigateur ne peut avoir qu'un seul thread de moteur JS exécutant le JS programme en même temps, c'est-à-dire que JS est monothreadLe thread du moteur JS et le thread de rendu de l'interface graphique s'excluent mutuellement, donc le moteur JS bloquera le rendu des pages
Le thread de déclenchement chronométré
est responsable de exécuter la fonction timer (setTimeout, setInterval) Le compteur de timing du navigateur n'est pas compté par le moteur JS (car JS est monothread, s'il est bloqué, cela affectera la précision du compteur))
Temps et déclencheur timing via un thread séparé (une fois le timing terminé, ajoutez-le à la file d'attente des événements du thread de déclenchement d'événement et attendez l'exécution une fois le moteur JS inactif. Ce thread est le thread de déclenchement de timing, également appelé thread de minuterie
W3C). Il est stipulé dans la norme HTML que l'intervalle de temps inférieur à 4 ms dans setTimeout est compté pour 4 ms
Le thread de déclenchement de l'événementest responsable de transmettre les événements préparés au thread du moteur JS pour exécution
Lorsque l'événement est déclenché , ce fil L'événement correspondant sera ajouté à la fin de la file d'attente à traiter, en attente du traitement du moteur JS
Fil de requête asynchroneAprès la connexion XMLHttpRequest, le navigateur ouvrira un fil
Lors de la détection de la requête changement de statut, s'il existe une fonction de rappel correspondante, le thread de requête asynchrone générera un événement de changement d'état et mettra la fonction de rappel correspondante dans la file d'attente pour attendre que le moteur JS s'exécute
Synchronisation et asynchrone
Étant donné que JavaScript est monothread, cela détermine que ses tâches ne peuvent pas être uniquement des tâches synchrones. Si les tâches qui prennent du temps sont également exécutées en tant que tâches synchrones, elles entraîneront un blocage de page. Par conséquent, les tâches JavaScript sont généralement divisées en deux catégories :
Tâches synchronesLes tâches synchrones font référence à, Pour les 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
Tâches asynchronesLes tâches asynchrones font référence à non ; en entrant dans le thread principal mais en entrant dans la tâche "file d'attente des tâches" (file d'attente des événements), ce n'est que lorsque la "file d'attente des tâches" informe le thread principal qu'une tâche asynchrone peut être exécutée que la tâche entrera dans le thread principal pour exécution.
Tâches asynchrones courantes : minuteries, ajax, liaison d'événements, fonctions de rappel, promesses, attente asynchrone, etc.
Les tâches synchrones et asynchrones entrent respectivement dans différents "lieux" d'exécution, entrant de manière synchrone dans le thread principal et entrant de manière asynchrone dans la table des événements et enregistrez la fonction.
Lorsque les éléments spécifiés dans le tableau des événements seront terminés, cette fonction sera déplacée vers la file d'attente des événements.
Une fois la tâche exécutée dans le thread principal, elle sera vide. Elle ira dans la file d'attente des événements pour lire la fonction correspondante et entrera dans le thread principal pour exécution.
Macro-tâches et micro-tâches
En plus des tâches synchrones et des tâches asynchrones au sens large, JavaScript dispose également de définitions de tâches plus détaillées :
Macro-tâche : incluant le code global, setTimeout, setInterval
Micro-tâche : new Promise().then(callback) process.nextTick() Différents types de tâches entreront dans différentes files d'attente de tâches :
Pile d'exécution
Le code JavaScript est exécuté dans un contexte d'exécution. Il existe trois contextes d'exécution en JavaScript :
Contexte d'exécution global
Contexte d'exécution de fonction Lorsqu'une fonction JS est appelée, un contexte d'exécution de fonction sera créé
contexte d'exécution eval, le contexte généré par la fonction eval (moins utilisé)
De manière générale, notre code JS a plus d'un contexte, alors quel est l'ordre d'exécution de ces contextes ?
Nous savons tous que la pile est une structure de données dernier entré, premier sorti. La pile d'exécution dans notre JavaScript est une telle structure de pile lorsque le moteur JS exécute le code, un contexte global est généré et inséré dans le code. pile d'exécution. Chaque fois qu'un appel de fonction est rencontré, le contexte d'exécution de la fonction est généré et poussé sur la pile d'exécution. Le moteur commence à exécuter la fonction depuis le haut de la pile et le contexte d'exécution apparaîtra après l'exécution.
function add(){ console.log(1) foo() console.log(3) } function foo(){ console.log(2) } add()
Jetons un coup d'œil à la pile d'exécution du code ci-dessus :
File d'attente des tâches
Nous avons mentionné plus tôt que toutes les tâches en JavaScript sont divisées en tâches synchrones et tâches asynchrones, tâches synchrones, comme comme son nom l'indique, est une tâche qui est exécutée immédiatement. Elle entre généralement directement dans le thread principal pour être exécutée. Notre tâche asynchrone entre dans la file d'attente des tâches et attend que la tâche dans le thread principal soit exécutée avant de l'exécuter.
La file d'attente des tâches est une file d'attente d'événements, indiquant que les tâches asynchrones associées peuvent entrer dans la pile d'exécution. Le thread principal lit la file d'attente des tâches pour lire les événements qu'elle contient.
La file d'attente est une structure de données premier entré, premier sorti.
Nous avons mentionné ci-dessus que les tâches asynchrones peuvent être divisées en macro-tâches et micro-tâches, de sorte que les files d'attente de tâches peuvent également être divisées en files d'attente de macro-tâches et en files d'attente de micro-tâches
File d'attente de macrotâches : pour les travaux à relativement grande échelle, les plus courantes incluent setTimeout, setInterval, interaction utilisateur, rendu de l'interface utilisateur, etc. ;
File d'attente Microtask : effectuez des travaux plus petits, les plus courants incluent Promise, Process.nextTick
Event-Loop
Les tâches de synchronisation sont directement placés dans le thread principal pour exécution, et les tâches asynchrones (événements de clic, minuteries, ajax, etc.) sont exécutées en arrière-plan, en attendant que les événements d'E/S se terminent ou que les événements comportementaux soient déclenchés.
Le système exécute des tâches asynchrones en arrière-plan. Si un événement de tâche asynchrone (ou un événement comportemental est déclenché), la tâche sera ajoutée à la file d'attente des tâches et chaque tâche sera traitée par une fonction de rappel.
Les tâches asynchrones ici sont divisées en macro-tâches et micro-tâches. Les macro-tâches entrent dans la file d'attente des macro-tâches et les micro-tâches entrent dans la file d'attente des micro-tâches.
Les tâches de la file d'attente des tâches d'exécution sont spécifiquement complétées dans la pile d'exécution. Lorsque toutes les tâches du thread principal sont exécutées, lisez la file d'attente des micro-tâches, elles seront toutes exécutées, puis. lisez les tâches macro. File d'attente
Le processus ci-dessus sera répété en continu, ce que nous appelons souvent boucle d'événement (Event-Loop).
Exemple de vérification de question
Regardons une question à vérifier
(async ()=>{ console.log(1) setTimeout(() => { console.log('setTimeout1') }, 0); function foo (){ return new Promise((res,rej) => { console.log(2) res(3) }) } new Promise((resolve,reject)=>{ console.log(4) resolve() console.log(5) }).then(()=> { console.log('6') }) const res = await foo(); console.log(res); console.log('7') setTimeout(_ => console.log('setTimeout2')) })()
L'ordre d'impression est : 1,4,5,2,6,3,7,setTimeout1,setTimeout2
Analyse :
Le code commence par le haut Exécutez ensuite, rencontrez d'abord console.log(1), imprimez 1 directement, puis rencontrez un minuteur qui appartient à une tâche macro, placez-le dans la file d'attente des tâches macro
Ensuite, rencontrez une promesse, car la nouvelle promesse est une tâche synchrone, alors imprimez 4 directement, lorsque vous rencontrez solve, qui est la fonction then suivante, placez-la dans la file d'attente des microtâches, imprimez 5
, puis exécutez wait foo. Il y a une promesse dans la fonction foo, et une nouvelle promesse est. une tâche synchrone, donc elle en imprimera 2 directement, et wait return est un rappel de promesse, la tâche après wait est placée dans la file d'attente des microtâches
Enfin, un minuteur est rencontré et placé dans la file d'attente des macrotâches
La tâche de la pile d'exécution est terminée , allez d'abord dans la file d'attente des microtâches pour obtenir l'exécution de la microtâche, exécutez-la d'abord. Pour la première microtâche, imprimez 6, puis exécutez la deuxième microtâche, imprimez 3, 7
Une fois la microtâche exécutée, allez dans la file d'attente des macrotâches pour obtenir le exécution de macrotâches, impression setTimeout1, setTimeout2
[Recommandations associées : Tutoriel d'étude javascript】
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!