Le contenu de cet article explique comment comprendre la boucle d'événements Javascript ? (Images et texte), il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.
Accéder aux données dans la mémoire de l'ordinateur Les structures de données de base sont divisées en piles et en files d'attente.
La pile est une structure de données dernier entré, premier sorti. Notez que parfois la pile est appelée une « pile », mais le « tas » est une autre structure de données complexe. Elle est complètement différente de la pile. Ce sont deux choses différentes. La particularité de la pile est que les opérations ne sont effectuées qu'à une seule extrémité. De manière générale, il n'y a que deux opérations de pile : push et pop. Les premières données placées sur la pile sont toujours les dernières à sortir.
La file d'attente est similaire à la pile, mais il s'agit d'une structure de données premier entré, premier sorti. L'opération d'insertion de données est effectuée à partir d'une seule. fin de la file d'attente, et l'opération de suppression est de l'autre côté.
Une métaphore courante est qu'une pile est comme un seau érigé. Les données placées dans la pile seront d'abord placées au fond du seau. . Les données sont extraites une par une à l'embouchure du compartiment, de sorte que les données placées en premier dans la pile sont toujours les dernières à être supprimées. La file d'attente est comme une conduite d'eau. Les premières données mises dans la file d'attente seront les premières à sortir de l'autre extrémité de la file d'attente. C'est la plus grande différence entre elles.
En JavaScript, l'exécution d'une fonction est un processus push et pop typique :
function fun1() { function fun2() { function fun3() { console.log('do it'); } fun3(); } fun2(); } fun1();
Lorsque le programme est exécuté, fun1, fun2 et fun3 sont d'abord poussés sur la pile dans séquence. Lors de l'appel d'une fonction, fun3 est appelé (sauté) en premier, puis fun2 et fun1. Imaginez, si fun1 est retiré de la pile en premier, alors les fonctions fun2 et fun3 seront perdues.
Dans le langage JavaScript, le programme est monothread et n'a qu'un seul thread principal. Pourquoi ? Car il n'est pas difficile d'imaginer que JavaScript a été conçu à l'origine pour être un langage de script qui s'exécute dans le navigateur. S'il est conçu pour être multithread et que deux threads modifient le DOM en même temps, quelle décision sera utilisée ? Par conséquent, JavaScript est monothread. Dans un thread, le code défilera phrase par phrase jusqu'à ce que le programme soit terminé. S'il y a des opérations plus longues au milieu, vous ne pouvez qu'attendre.
La conception monothread rend l'efficacité d'exécution du langage très médiocre. Afin de profiter des performances des processeurs multicœurs, le langage JavaScript prend en charge le code asynchrone lorsqu'il y a des opérations plus chronophages. , la tâche peut être écrite pour une exécution asynchrone. Lorsque la tâche asynchrone n'a pas été exécutée, le thread principal suspendra la tâche asynchrone et continuera à exécuter le code synchrone suivant. Ensuite, revenez en arrière et exécutez-le à nouveau s'il y a un. tâche asynchrone dont l'exécution est terminée.
Cette façon d'exécuter du code est en fait très cohérente avec de nombreuses scènes de nos vies. Par exemple, Xiao Ming rentre du travail, il a très soif et veut faire bouillir de l'eau pour le thé. méthode d'exécution synchrone, il fera bouillir l'eau. , quand l'eau ne bout pas, Xiao Ming attend comme un imbécile, attendant que l'eau bout avant de faire du thé si elle est exécutée de manière asynchrone, Xiao Ming commence d'abord à faire bouillir l'eau, et ; puis va faire d'autres choses, comme regarder la télévision ou écouter de la musique. Attendez que l'eau bout avant de faire du thé. Évidemment, la deuxième méthode asynchrone est plus efficace.
Quelles sont les opérations asynchrones courantes ? Il en existe de nombreux, nous pouvons en citer quelques-uns courants :
Ajax
Opération événementielle DOM
setTimeout
Méthode then de la promesse
Fichier lu du nœud
Prenons d'abord un regardez un morceau de code :
//示例1 console.log(1); setTimeout(function () { console.log(2); }, 1000); console.log(3);
Ce code est très simple. Le résultat de son exécution dans le navigateur est le suivant :
1 3 2
Parce que la fonction setTimeout retarde l'exécution de. 1000 millisecondes, donc 1 et 3 sont sortis en premier, et 2 est sorti après 1000 millisecondes, ce qui est très logique.
Modifions légèrement le code et changeons le temps de retard de setTimeout à 0 :
//示例2 console.log(1); setTimeout(function () { console.log(2); }, 0); //0毫秒,不延时 console.log(3);
Résultats d'exécution :
1 3 2
Pourquoi le délai est-il toujours de 0 milliseconde ? La dernière sortie de 2 ? Ne vous inquiétez pas, jetons un coup d'œil à un morceau de code :
//示例3 console.log(1); setTimeout(function () { console.log(2); }, 0); Promise.resolve().then(function(){ console.log(3); }); console.log(4);
Exécuter le résultat :
1 4 3 2
Les trois morceaux de code ci-dessus, si vous pouvez écrire les résultats correctement et expliquez pourquoi Une sortie comme celle-ci montre que vous avez une compréhension claire de la boucle d'événements JavaScript. Si vous ne pouvez pas l'expliquer, parlons de ce qui s'est passé ici. C'est en fait très intéressant.
Au début, parlons brièvement de la structure de base des données. Cela a-t-il quelque chose à voir avec la boucle d'événements dont nous parlons maintenant ? Bien sûr que si. La première chose à préciser est que le code javascript est entièrement exécuté sur la pile Qu'il s'agisse de code synchrone ou de code asynchrone, cela doit être clair.
Nous pouvons généralement diviser le code en code synchrone et code asynchrone. En fait, le code asynchrone peut être divisé en deux catégories : Macro-tâches et Micro-tâches.
Ne vous inquiétez pas de ce que sont les macro-tâches et les micro-tâches. Souvent, des termes aussi généraux ne sont pas propices à notre compréhension : macro signifie macro et grand signifie micro et petit ; .
Javascript est un langage interprété, et son processus d'exécution est le suivant :
从上到下依次解释每一条js语句
若是同步任务,则压入一个栈(主线程);如果是异步任务,就放到一个任务队列里
开始执行栈里的同步任务,直到将栈里的所有任务都走完,此时栈清空了
回过头看异步队列里如果有异步任务完成了,就生成一个事件并注册回调,压入栈中
再返回第3步,直到异步队列都清空,程序运行结束
语言描述的费劲,不如看图:
通过以上的步骤可以看到,不论是同步还是异步,只要是执行的时候都是要在栈里执行的,而一遍又一遍的回头检查异步队列,这种执行方式 就是所谓的“事件环”。
明白了javascript的执行原理,我们就不难理解之前的第二段代码,为什么setTimeout为0时会最后执行,因为setTimeout是异步代码,必须要等所有的同步代码都执行完,才会执行异步队列。即使setTimeout执行得再快,它也不可能在同步代码之前执行。
聊了这么多,我们好像还没有说宏任务和微任务的话题呢,上面说了,异步任务又分为微任务和宏任务,那它们又是一个怎样的执行机制呢?
注意!微任务和宏任务的执行方式在浏览器和Node中有差异,有差异!重要的事我们多说几遍,以下我们讨论的是在浏览器的环境里。
在浏览器的执行环境中,总是先执行小的、微任务,再执行大的、宏任务,回过头再看看第三段代码,为什么Promise的then方法在setTimeout之前执行?其根本原理就是因为Promise的then方法是一个微任务,而setTimeout是一个宏任务。
接下来我们借用阮一峰老师的一张图来说明:
其实,以上这张图示我们可以再将它细化一点,这个图上的异步队列只画了一个,也就是说没有区分微任务队列和宏任务队列。我们可以脑补一下,在此图上多加一个微任务队列,当javascript执行时再多加一个判断,如果是微任务就加到微任务队列里,宏任务就加到宏任务队列里,在清空队列时,浏览器总会优先清空“微任务”。这样就把浏览器的事件环撤底说全了。
最后来一个大考,以下代码的运行结果是什么:
<script> setTimeout(function () { console.log(1); Promise.resolve().then(function () { console.log(2); }); }); setTimeout(function () { console.log(3); }); Promise.resolve().then(function () { console.log(4); }); console.log(5); </script>
将此代码拷到chrome中跑一下,结果是:
5 4 1 2 3
不妨我们试着分析一下为什么是这个结果,首先输出5,因为console.log(5)
是同步代码,这没什么可说的。
之后将前两个setTimeout和最后一个Promise放入异步队列,注意它们的区分,此时执行完了同步代码之后发现微任务和宏任务队列中都有代码,按浏览器的事件环机制,优先执行微任务,此时输出4。
然后执行宏任务队列里的第一个setTimeout,输出1。
此时,setTimeout中又有一个Promise,放入微任务队列。
再次清空微任务队列,输出2。
最后宏任务队列里还有最后一个setTimeout,输出3。
而Node中的事件环又和浏览器有些许的不同,在node.js的官方文档中有专门的描述,其中文档中有一张图,详细的说明了它的事件环机制,我们把它拿出来:
可以看到,node.js中的事件环机制分为了6个阶段,其中最重要的3个阶段我在上面做了注明:
timer阶段,指的就是setTimeout等宏任务
poll轮询阶段,如读取文件等宏任务
check阶段,setImmediate宏任务
图中每一个阶段都代表了一个宏任务队列,在Node事件环中,微任务的运行时机是在每一个“宏任务队列”清空之后,在进入下一个宏任务队列之间执行。这是和浏览器的最大区别。
还是用代码说话吧,有一道经典的Node.js事件环面试题:
const fs = require('fs'); fs.readFile('./1.txt', (err, data) => { setTimeout(() => { console.log('timeout'); }); setImmediate(() => { console.log('immediate'); }); Promise.resolve().then(() => { console.log('Promise'); }); });
运行结果:
Promise immediate timeout
代码并不复杂,首先使用fs模块读取了一个文件,在回调的内部有两个宏任务和一个微任务,微任务总是优于宏任务执行的,因此先输出Promise。
但是之后的区别为什么先输出immdiate?原因就在于fs读取文件的宏任务在上图中的第4个轮询阶段,当第4个阶段清空队列之后,就该进入第5个check阶段,也就是setImmediate这个宏任务所在的阶段,而不会跳回第1个阶段,因此先输出immedate。
最后总结一下,分析完浏览器和Node的事件环发现它们并不简单,但只要记住了它们之间的区别就可以分析出结果。
浏览器事件环是运行完一个宏任务马上清空微任务队列。
Node事件环是清空完一个阶段的宏任务队列之后再清空微任务队列。
最后,总结一下常见的宏任务和微任务:
宏任务 | 微任务 |
---|---|
setTimeout | Promise的then方法 |
setInterval | process.nextTick |
setImmediate | MutationObserver |
MessageChannel |
相关推荐:
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!