Maison > Article > interface Web > Une explication approfondie du mécanisme de synchronisation JavaScript (y compris le code)
Cet article présente principalement une compréhension approfondie du mécanisme de synchronisation JavaScript, ce qui est utile pour apprendre JavaScript. Les amis intéressés peuvent s'y référer.
Cet article présente le mécanisme de synchronisation de JavaScript. Pour comprendre le mécanisme de synchronisation de JavaScript, vous devez connaître le mécanisme d'exécution de JavaScript.
Tout d'abord, JavaScript est piloté par les événements en s'exécutant dans un seul thread (thread du moteur JavaScript).
1. Il existe plusieurs fils de discussion dans le navigateur
Les fils de discussion les plus basiques inclus dans un navigateur :
1.
2. Le fil Timer, setInterval et setTimeout déclencheront ce fil.
3. Fil de déclenchement d'événement du navigateur, ce fil déclenchera onclick, onmousemove et d'autres événements du navigateur.
4. Le fil de rendu de l'interface est responsable du rendu des éléments HTML de l'interface du navigateur. Remarque : Pendant que le moteur JavaScript exécute le script, le thread de rendu de l'interface est dans un état suspendu. C'est-à-dire que lorsque JavaScript est utilisé pour faire fonctionner des nœuds dans l'interface, cela ne sera pas reflété immédiatement tant que le thread du moteur JavaScript ne sera pas inactif. (C'est la fin)
5. Fil de requête HTTP (les requêtes Ajax en font aussi partie).
Les threads ci-dessus coopèrent les uns avec les autres pour terminer le travail sous le contrôle du noyau du navigateur (je ne connais pas les détails).
2. File d'attente des tâches
Nous savons que JavaScript est monothread et que tout le code JavaScript s'exécute dans le thread du moteur JavaScript. L'article de l'enseignant Ruan Yifeng appelle ce fil le fil principal, qui est une pile d'exécution. (Le contenu suivant est principalement basé sur la compréhension et le résumé de l'article du professeur Ruan Yifeng.)
Nous pouvons considérer ces codes JavaScript comme des tâches une par une. Ces tâches sont divisées en tâches synchrones et tâches asynchrones. Les tâches synchrones (telles que les instructions d'affectation de variables, les instructions d'alerte, les instructions de déclaration de fonction, etc.) sont exécutées directement sur le thread principal en séquence, et les tâches asynchrones (telles que divers événements déclenchés par le thread déclencheur d'événements du navigateur, utilisent la réponse du serveur renvoyée). par Ajax etc.) sont mis en file d'attente dans la file d'attente des tâches (également appelée file d'attente des événements, file d'attente des messages) par ordre chronologique, en attente d'exécution. Tant que les tâches sur le thread principal sont exécutées, la file d'attente des tâches sera vérifiée pour voir s'il y a des tâches en attente dans la file d'attente. Si tel est le cas, les tâches en file d'attente entreront dans le thread principal pour exécution.
Par exemple, l'exemple suivant :
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>定时机制</title> <style type="text/css"> body{ margin: 0; padding: 0; position: relative; height: 600px; } #test{ height: 30px; width: 30px; position: absolute; left: 0; top: 100px; background-color: pink; } </style> </head> <body> <p id="test"> </p> <script> var pro = document.getElementById('test'); pro.onclick = function() { alert('我没有立即被执行。'); }; function test() { a = new Date(); var b=0; for(var i=0;i<3000000000;i++) { b++; } c = new Date(); console.log(c-a); } test(); </script> </body> </html>
Dans cet exemple, il faut environ 8 à 9 secondes pour que la fonction test() soit exécutée, donc lorsque nous ouvrons cette page, cliquez sur la couleur rose avant 8 secondes. Pour les carrés, la boîte de dialogue n'apparaîtra pas immédiatement, mais apparaîtra après 8 secondes. De plus, si vous cliquez plusieurs fois sur la case rose avant 8 secondes, elle apparaîtra plusieurs fois après 8 secondes. .
Lorsque nous ouvrons cette page, le thread principal déclare d'abord la fonction test, puis déclare la variable pro, puis attribue le nœud p à pro, puis ajoute un événement click au nœud p et spécifie une fonction de rappel. (suspension), puis appelez la fonction de test et exécutez le code qu'elle contient. Lors de l'exécution du code dans la fonction test, nous avons cliqué sur le nœud p. Le thread déclencheur d'événement du navigateur a détecté cet événement et a placé l'événement dans la file d'attente des tâches afin que la tâche sur le thread principal (ici la fonction test) soit terminé. Enfin, cet événement est trouvé lors de la vérification de la file d'attente des tâches et la fonction de rappel correspondante est exécutée. Si nous cliquons plusieurs fois, ces multiples événements déclenchés seront mis en file d'attente dans la file d'attente des tâches en fonction de l'heure du déclenchement (vous pouvez ajouter un événement de clic à un autre élément et cliquer alternativement sur différents éléments pour vérifier).
Ce qui suit est un résumé du mécanisme de fonctionnement de la tâche :
Le mécanisme de fonctionnement de l'exécution asynchrone est le suivant. (Il en va de même pour l'exécution synchrone, car elle peut être considérée comme une exécution asynchrone sans tâches asynchrones.)
1. Toutes les tâches synchrones sont exécutées sur le thread principal, formant une pile de contexte d'exécution.
2. En plus du fil de discussion principal, il existe également une "file d'attente des tâches". Tant que la tâche asynchrone a des résultats en cours d'exécution, un événement est placé dans la « file d'attente des tâches ».
3. Une fois que toutes les tâches de synchronisation dans la « pile d'exécution » sont terminées, le système lira la « file d'attente des tâches » pour voir quels événements s'y trouvent. Les tâches asynchrones correspondantes mettent fin à l'état d'attente, entrent dans la pile d'exécution et démarrent l'exécution.
4. Le fil principal continue de répéter la troisième étape ci-dessus.
3. Événements et fonctions de rappel
Lorsque nous spécifions un événement pour un élément DOM, nous spécifierons une fonction de rappel afin que le code correspondant puisse être exécuté lorsque le l’événement se produit réellement.
La fonction de rappel de l'événement dans le thread principal sera suspendue. S'il y a un événement correspondant mis en file d'attente dans la file d'attente des tâches, la fonction de rappel correspondante sera exécutée lorsque le thread principal le détectera. On peut également dire que le thread principal effectue des tâches asynchrones, qui exécutent la fonction de rappel correspondante.
4. Boucle d'événements
Le processus par lequel le thread principal vérifie les événements dans la file d'attente des tâches est cyclique, nous pouvons donc dessiner un diagramme de la boucle d'événements :
上图中主线程产生堆和执行栈,栈中的任务执行完毕后,主线程检查任务队列中由其他线程传入的发生过的事件,检测到排在最前面的事件,就从挂起的回调函数中找出与该事件对应的回调函数,然后在执行栈中执行,这个过程一直重复。
五、定时器
结合以上知识,下面探讨JavaScript中的定时器:setTimeout()和setInterval()。
setTimeout(func, t)是超时调用,间隔一段时间后调用函数。这个过程在事件循环中的过程如下(我的理解):
主线程执行完setTimeout(func, t);语句后,把回调函数func挂起,同时定时器线程开始计时,当计时等于t时,相当于发生了一个事件,这个事件传入任务队列(结束计时,只计时一次),当主线程中的任务执行完后,主线程检查任务队列发现了这个事件,就执行挂起的回调函数func。我们指定的时间间隔t只是参考值,真正的时间间隔取决于执行完setTimeout(func, t);语句后的代码所花费的时间,而且是只大不小。(即使我们把t设为0,也要经历这个过程)。
setInterval(func, t)是间歇调用,每隔一段时间后调用函数。这个过程在事件循环中的过程与上面的类似,但又有所不同。
setTimeout()是经过时间t后定时器线程在任务队列中添加一个事件(注意是一个),而setInterval()是每经过时间t(一直在计时,除非清除间歇调用)后定时器线程在任务队列中添加一个事件,而不管之前添加的事件有没有被主线程检测到并执行。(实际上浏览器是比较智能的,浏览器在处理setInterval的时候,如果发现已任务队列中已经有排队的同一ID的setInterval的间歇调用事件,就直接把新来的事件 Kill 掉。也就是说任务队列中一次只能存在一个来自同一ID的间歇调用的事件。)
举个例子,假如执行完setInterval(func, t);后的代码要花费2t的时间,当2t时间过后,主线程从任务队列中检测到定时器线程传入的第一个间歇调用事件,func开始执行。当第一次的func执行完毕后,第二次的间歇调用事件早已传入任务队列,主线程马上检测到第二次的间歇调用事件,func函数又立即执行。这种情况下func函数的两次执行是连续发生的,中间没有时间间隔。
下面是个例子:
function test() { a = new Date(); var b=0; for(var i=0;i<3000000000;i++) { b++; } c = new Date(); console.log(c-a); } function test2() { var d = new Date().valueOf(); //var e = d-a; console.log('我被调用的时刻是:'+d+'ms'); //alert(1); } setInterval(test2,3000); test();
结果:
为什么8.6秒过后没有输出两个一样的时刻,原因在上面的内容中可以找到。
执行例子中的for循环花费了8601ms,在执行for循环的过程中队列中只有一个间歇调用事件在排队(原因如上所述),当8601ms过后,第一个间歇调用事件进入主线程,对于这个例子来说此时任务队列空了,可以再次传入间歇调用事件了,所以1477462632228ms这个时刻第二次间歇调用事件(实际上应该是第三次)传入任务队列,由于主线程的执行栈已经空了,所以主线程立即把对应的回调函数拿来执行,第二次调用与第一次调用之间仅仅间隔了320ms(其实8601+320=8920,差不多就等于9秒了)。我们看到第三次调用已经恢复正常了,因为此时主线程中已经没有其他代码了,只有一个任务,就是隔一段时间执行一次间歇调用的回调函数。
用setTimeout()实现间歇调用的例子:
function test() { a = new Date(); var b=0; for(var i=0;i<3000000000;i++) { b++; } c = new Date(); console.log(c-a); } function test2(){ var d = new Date().valueOf(); console.log('我被调用的时刻是:'+d+'ms'); setTimeout(test2,3000); } setTimeout(test2,3000); test();
结果:
每两次调用的时间间隔基本上是相同。想想为什么?
再看一个例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flex布局练习</title> <style type="text/css"> body{ margin: 0; padding: 0; position: relative; height: 600px; } #test{ height: 30px; width: 30px; position: absolute; left: 0; top: 100px; background-color: pink; } </style> </head> <body> <p id="test"> </p> <script> var p = document.createElement('p'); p.style.width = '50px'; p.style.height = '50px'; p.style.border = '1px solid black'; document.body.appendChild(p); alert('ok'); </script> </body> </html>
这个例子的结果是提示框先弹出,然后黑色边框的p元素才出现在页面中。原因很简单,就一句话:
在JavaScript引擎运行脚本期间,界面渲染线程都是处于挂起状态的。也就是说当使用JavaScript对界面中的节点进行操作时,并不会立即体现出来,要等到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!