ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のタイミングメカニズムについての深い理解

JavaScript のタイミングメカニズムについての深い理解

高洛峰
高洛峰オリジナル
2016-12-08 15:14:241338ブラウズ

この記事では、JavaScript のタイミング メカニズムを紹介します。JavaScript のタイミング メカニズムを理解するには、JavaScript の実行メカニズムを理解する必要があります。

まず、JavaScript はシングルスレッド (JavaScript エンジンのスレッド) イベント駆動型であると述べられています。

1. ブラウザには複数のスレッドがあります

ブラウザに含まれる最も基本的なスレッド:

1.

2. タイマー スレッド、setInterval および setTimeout がこのスレッドをトリガーします。

3. ブラウザ イベント トリガー スレッド。このスレッドは、onclick、onmousemove、その他のブラウザ イベントをトリガーします。

4. インターフェイス レンダリング スレッドは、ブラウザ インターフェイスの HTML 要素をレンダリングします。注: JavaScript エンジンがスクリプトを実行している間、インターフェイス レンダリング スレッドは一時停止状態になります。つまり、JavaScript を使用してインターフェース内のノードを操作しても、すぐには反映されません。JavaScript エンジンのスレッドがアイドル状態になるまで反映されません。 (これは最後に言います)

5. HTTP リクエスト スレッド (Ajax リクエストも含まれます)。

上記のスレッドはブラウザカーネルの制御下で相互に連携して作業を完了します(詳細はわかりません)。

2. タスク キュー

JavaScript はシングルスレッドであり、すべての JavaScript コードは JavaScript エンジン スレッドで実行されることがわかっています。 Ruan Yifeng 先生の記事では、このスレッドを実行スタックであるメイン スレッドと呼んでいます。 (以下の内容は、主にRuan Yifeng先生の記事の理解と要約に基づいています。)

これらのJavaScriptコードを1つずつタスクとして考えることができます。これらのタスクは、同期タスクと非同期タスクに分けられます。同期タスク (変数代入文、アラート文、関数宣言文など) はメインスレッド上で直接順番に実行され、非同期タスク (ブラウザのイベントトリガースレッドによってトリガーされるさまざまなイベントなど、返されたサーバー応答を使用します) Ajax などによる)は、時系列順にタスクキュー(イベントキュー、メッセージキューとも呼ばれます)にキューイングされ、実行を待ちます。メインスレッド上のタスクが実行されている限り、タスクキューがチェックされ、キュー内に待機中のタスクがあるかどうかが確認されます。存在する場合、キューに入れられたタスクは実行のためにメインスレッドに入ります。

たとえば、次の例:

<!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>
  <div id="test">
   
  </div>
 
<script>
  var pro = document.getElementById(&#39;test&#39;);
  pro.onclick = function() {
    alert(&#39;我没有立即被执行。&#39;);
  };
  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>

この例では、test() 関数の実行に約 8 ~ 9 秒かかるため、このページを開いて 8 秒前のピンク色の四角をクリックすると、プロンプト ボックスは 8 秒後までポップアップしません。8 秒前にピンクのボックスを数回クリックすると、8 秒後に数回ポップアップします。

このページを開くと、メインスレッドは最初に関数 test を宣言し、次に変数 pro を宣言し、次に p ノードを pro に割り当て、次にクリック イベントを p ノードに追加し、コールバック関数 (一時停止) を指定します。次に、テスト関数を呼び出し、その中のコードを実行します。テスト関数のコードの実行中に、p ノードをクリックすると、ブラウザーのイベント トリガー スレッドがこのイベントを検出し、メイン スレッド (ここではテスト関数) 上のタスクが実行されるようにイベントをタスク キューに配置しました。最後に、このイベントはタスク キューをチェックするときに検出され、対応するコールバック関数が実行されます。複数回クリックすると、トリガーされた複数のイベントがトリガー時間に従ってタスク キューに入れられます (クリック イベントを別の要素に追加し、別の要素を交互にクリックして確認できます)。

タスクの動作メカニズムをまとめると以下の通りです:

非同期実行の動作メカニズムは以下の通りです。 (同期実行についても同様です。非同期タスクがなければ非同期実行とみなすことができるためです。)

1. すべての同期タスクはメインスレッドで実行され、実行コンテキストスタックを形成します。

2. メインスレッドに加えて、「タスクキュー」もあります。非同期タスクに実行結果がある限り、イベントは「タスク キュー」に配置されます。

3. 「実行スタック」内のすべての同期タスクが完了すると、システムは「タスクキュー」を読み取り、その中にどのようなイベントがあるかを確認します。これらの対応する非同期タスクは待機状態を終了し、実行スタックに入り、実行を開始します。

4. メインスレッドは上記の 3 番目のステップを繰り返します。

3. イベントとコールバック関数

DOM 要素にイベントを指定するときは、イベントが実際に発生したときに対応するコードが実行できるようにコールバック関数を指定します。

メインスレッドのイベントのコールバック関数は、タスクキューにキューイングされている対応するイベントがある場合、メインスレッドがそれを検出したときに、対応するコールバック関数が実行されます。メインスレッドは非同期タスクを実行し、対応するコールバック関数を実行しているとも言えます。

4. イベント ループ

タスク キュー内のイベントをチェックするメイン スレッドのプロセスは周期的であるため、イベント ループの図を描くことができます:

JavaScript のタイミングメカニズムについての深い理解

上の図では、メイン スレッドヒープと実行スタックを生成し、スタック内のタスクが実行された後、メインスレッドはタスクキュー内で発生し、他のスレッドから渡されたイベントをチェックし、先頭のイベントが検出された場合にそれを検出します。一時停止されたコールバック関数からのイベントに対応するイベントが実行スタック上で実行され、このプロセスが繰り返されます。

5. タイマー


结合以上知识,下面探讨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(&#39;我被调用的时刻是:&#39;+d+&#39;ms&#39;);
   //alert(1);
  }
  setInterval(test2,3000);
   
 test();

   

结果:

JavaScript のタイミングメカニズムについての深い理解

为什么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(&#39;我被调用的时刻是:&#39;+d+&#39;ms&#39;);
   setTimeout(test2,3000);
  }
  setTimeout(test2,3000);
 test();

   

 结果:

JavaScript のタイミングメカニズムについての深い理解

每两次调用的时间间隔基本上是相同。想想为什么?

再看一个例子:

<!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>
  <div id="test">
   
  </div>
 
<script>
 var p = document.createElement(&#39;p&#39;);
 p.style.width = &#39;50px&#39;;
 p.style.height = &#39;50px&#39;;
 p.style.border = &#39;1px solid black&#39;;
  
 document.body.appendChild(p);
 
 alert(&#39;ok&#39;);
  
</script>
</body>
</html>

   

这个例子的结果是提示框先弹出,然后黑色边框的p元素才出现在页面中。原因很简单,就一句话:

在JavaScript引擎运行脚本期间,界面渲染线程都是处于挂起状态的。也就是说当使用JavaScript对界面中的节点进行操作时,并不会立即体现出来,要等到JavaScript引擎线程空闲时,才会体现出来。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。