Cet article vous apporte une explication détaillée du mécanisme de boucle d'événements des navigateurs JavaScript. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
Question au début :
Comment un seul thread peut-il être asynchrone ?
Quel est le processus de la boucle d'événements ?
Que sont les macrotâches et microtâche, et qu'est-ce que c'est ? Quelle est la différence
Mono-thread et asynchrone
Quand vous mentionnez js, vous penserez à mono-thread et asynchrone Donc. comment un seul thread atteint-il l'asynchronisme ? Concept d'abord, nous devons d'abord comprendre la relation entre un thread unique et asynchrone.
Les tâches JS sont divisées en deux types : synchrones et asynchrones, et leurs méthodes de traitement sont également différentes. Les tâches synchrones sont directement mises en file d'attente pour être exécutées sur le thread principal, tandis que les tâches asynchrones seront placées dans la file d'attente des tâches. il y a plusieurs tâches (tâches asynchrones) qui doivent être mises en file d'attente et en attente dans la file d'attente des tâches. La file d'attente des tâches est similaire à un tampon. La tâche sera déplacée vers la pile d'appels (pile d'appels) à l'étape suivante, puis vers la pile principale. le thread exécute la tâche de la pile d’appels.
Un seul thread signifie qu'il n'y a qu'un seul thread (thread principal) dans le moteur js qui est responsable de l'analyse et de l'exécution du code js, c'est-à-dire qu'il ne peut faire qu'une seule chose à la fois, et nous le savons pour une requête ajax, le thread principal attend sa réponse. En même temps, il fera autre chose. Le navigateur enregistre d'abord la fonction de rappel ajax dans la table des événements. Après le retour de la réponse, la fonction de rappel est ajoutée au fichier. file d'attente des tâches et attend l'exécution. Cela ne provoquera pas de blocage de thread, donc la façon dont js gère les requêtes ajax est asynchrone.
En bref, le processus permettant de vérifier si la pile d'appels est vide et de déterminer quelle tâche ajouter à la pile d'appels est la boucle d'événements, et le cœur de l'implémentation asynchrone de js est la boucle d'événements.
Pile d'appels et file d'attente de tâches
Comme son nom l'indique, la pile d'appels est une structure de pile. Les appels de fonction formeront un cadre de pile, qui contient des informations contextuelles telles que des paramètres et des variables locales du. fonction actuellement exécutée. Une fois la fonction exécutée, son contexte d'exécution est extrait de la pile.
L'image suivante est la relation entre la pile d'appels et la file d'attente des tâches
Boucle d'événements
À propos la boucle d'événements, Introduction aux spécifications HTML
Il doit y avoir au moins une boucle d'événements par agent utilisateur, et au maximum
une boucle d'événements par unité de contextes de navigation d'origine similaire associés.
Une boucle d'événements comporte une ou plusieurs files d'attente de tâches.
Chaque tâche est définie comme provenant d'une source de tâches spécifique.
Compris à partir du spécification , le navigateur a au moins une boucle d'événements et chaque boucle d'événements a au moins une file d'attente de tâches (macrotâche). Chaque tâche externe a son propre groupe et le navigateur définira les priorités pour différents groupes de tâches.
macrotâche & microtâche
La spécification mentionne deux concepts, mais ne les introduit pas en détail Après avoir consulté quelques informations, elle peut se résumer ainsi :
macrotâche : contient. le code js qui exécute l'intégralité des rappels d'événements, des rappels XHR, des minuteries (setTimeout/setInterval/setImmediate), des opérations IO, du rendu de l'interface utilisateur
microtâche : tâches qui mettent à jour l'état de l'application, y compris les rappels de promesse, MutationObserver, le processus. nextTick, Object.observe
Parmi eux, setImmediate et process.nextTick sont l'implémentation de nodejs, qui sera présentée en détail dans l'article nodejs.
Processus de traitement des événements
Concernant la compréhension de la macrotâche et de la microtâche, cela peut être un peu obscur si vous le regardez de cette façon, cependant, c'est beaucoup plus clair lorsqu'il est combiné avec le mécanisme de l'événement. boucle. L'image suivante peut être considérée comme une introduction très claire.
Pour résumer, les étapes d'une boucle d'événement comprennent :
1. Vérifiez si la file d'attente des macrotâches est vide. vide, allez à 2, qui est Si vide, allez à 3
2 Exécutez une tâche dans la macrotâche
3 Continuez à vérifier si la file d'attente des microtâches est vide, si c'est le cas, allez à 4, sinon allez à. 5
4. Supprimez la tâche dans la microtâche et exécutez Terminer et revenez à l'étape 3
5 Effectuer la mise à jour de la vue
Ordre d'exécution de mactotask et microtâche
.
Après avoir lu tant de trucs secs Pour présenter le concept, pourquoi ne pas regarder un morceau de code pour expérimenter
console.log('start') setTimeout(function() { console.log('setTimeout') }, 0) Promise.resolve().then(function() { console.log('promise1') }).then(function() { console.log('promise2') }) console.log('end')
Quelle est la séquence de journaux sortie par la station d'impression ? Sur la base de l'analyse des étapes ci-dessus, c'est si simple~
Tout d'abord, le code global (main()) est poussé dans la pile d'appels pour exécution et démarre est imprimé;
Ensuite, setTimeout est poussé dans la file d'attente des macrotâches, promise.then callback est placé dans la file d'attente des microtâches, et enfin console.log('end') est exécuté pour imprimer end
À ce stade, le code dans la pile d'appels a été exécuté. En repensant à la définition de la macrotâche, nous savons que le code global appartient à la macrotâche. Une fois la macrotâche exécutée, l'étape suivante consiste à exécuter la tâche de. la file d'attente des microtâches Exécutez le rappel de promesse et imprimez la promesse1 ;
promise回调函数默认返回undefined,promise状态变为fullfill触发接下来的then回调,继续压入microtask队列,event loop会把当前的microtask队列一直执行完,此时执行第二个promise.then回调打印出promise2;
这时microtask队列已经为空,从上面的流程图可以知道,接下来主线程会去做一些UI渲染工作(不一定会做),然后开始下一轮event loop,执行setTimeout的回调,打印出setTimeout;
这个过程会不断重复,也就是所谓的事件循环。
视图渲染的时机
回顾上面的事件循环示意图,update rendering(视图渲染)发生在本轮事件循环的microtask队列被执行完之后,也就是说执行任务的耗时会影响视图渲染的时机。通常浏览器以每秒60帧(60fps)的速率刷新页面,据说这个帧率最适合人眼交互,大概16.7ms渲染一帧,所以如果要让用户觉得顺畅,单个macrotask及它相关的所有microtask最好能在16.7ms内完成。
但也不是每轮事件循环都会执行视图更新,浏览器有自己的优化策略,例如把几次的视图更新累积到一起重绘,重绘之前会通知requestAnimationFrame执行回调函数,也就是说requestAnimationFrame回调的执行时机是在一次或多次事件循环的UI render阶段。
以下代码可以验证
setTimeout(function() {console.log('timer1')}, 0) requestAnimationFrame(function(){ console.log('requestAnimationFrame') }) setTimeout(function() {console.log('timer2')}, 0) new Promise(function executor(resolve) { console.log('promise 1') resolve() console.log('promise 2') }).then(function() { console.log('promise then') }) console.log('end')
可以看到,结果1中requestAnimationFrame()是在一次事件循环后执行,而在结果2,它的执行则是在三次事件循环结束后。
总结
1、事件循环是js实现异步的核心
2、每轮事件循环分为3个步骤:
a) 执行macrotask队列的一个任务
b) 执行完当前microtask队列的所有任务
c) UI render
3、浏览器只保证requestAnimationFrame的回调在重绘之前执行,没有确定的时间,何时重绘由浏览器决定
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!