イベントループとは
- すべてのブラウザーには少なくとも 1 つのイベント ループがあり、イベント ループには少なくとも 1 つのタスク キューがあります。
循环
それは常に「無限ループ」にあることを意味します。登録されたコールバック関数を実行スタックに継続的にプッシュします - ブラウザのイベントループの規格はHTML規格で規定されていますが、NodeJSのイベントループは実際には少し異なります
イベントループの理由
まず、簡単なコードを見てみましょう。
function a1(){
console.log('1')
}
function a2(){
console.log('2')
}
function a3(){
console.log('3')
a1()
a2()
}
a3()
出力:
このコードが実行されるプロセスは?
まず第一に、ブラウザーが JS スクリプトを実行したい場合、JS スクリプト (基本的にはプレーン テキスト) をマシンが理解して実行できるコンピューター命令に変換する「もの」が必要です。この「もの」が JS エンジンであり、実際に JS スクリプトをコンパイルして実行します。
v8 エンジンには、2 つの非常にコアなコンポーネント执行栈
と堆
. 実行中のコードは実行スタックに格納され、変数の値は通常は不規則なヒープに格納されます。
V8 がこのコードを実行すると、最初に呼び出されますa3()
。a3()
内部ではfirst が呼び出されa1()
、続いてa2()
a3 时,第一个帧被创建并压入栈中,帧中包含了
a1 的参数和局部变量。 当
a3 调用
a1 时,第二个帧被创建并被压入栈中,放在第一个帧之上,帧中包含
a1 的参数和局部变量。当
a1 执行完毕然后返回时,第二个帧就被弹出栈(剩下
a3 a3`の呼び出しも函数的调用帧 )当a3调用a2时,和第二帧同理,。当
実行されてから戻ると、最初のフレームもポップされ、スタックがクリアされます。
DOM および Web API
js エンジンは js スクリプトを実行するのに役立ちますが、私たちの目標は「ユーザー インターフェイスを構築する」ことであり、従来のフロントエンド インターフェイスは DOM に基づいて構築されています。DOM はドキュメント オブジェクト モデルであり、一連のインターフェイスを提供します。 js から直接呼び出すことができますが、js を呼び出すことができる DOM インターフェイスに加えて、ブラウザーはいくつかの WEB API も提供します。DOM も WEB も API も、本質的に JS とは何の関係もありません。
V8 はエンジンであり、JS コードを実行するために使用されます。ブラウザと Node は JS の実行環境であり、JS が呼び出すことができるいくつかの API を提供します。
ブラウザの存在により、JSでDOMやWEB APIを操作できるようになり、ユーザーインターフェースを構築できるようになったそうです。V8 にはスタックとヒープしかなく、イベント ループ、DOM、WEB API などの他のイベントについては何も知らないということを事前に明確にしておく必要があります。その理由は前述したように、V8 は JS コードのコンパイルと実行のみを担当するため、V8 に JS コードを渡すと、途中で停止することなく最初から最後まで実行されます。
非同期のマルチスレッド
依存関係のない複数のリクエストを前提として、マルチスレッド同期は複数のコード ブロックを同時に実行できます。最適な状況は、最も遅い状況によって異なります。
シングルスレッド非同期 (イベント ループ)
イベント ループはどのように非同期性を実現しますか?
ブラウザには JS スレッドが 1 つしかないことがわかっています。イベント ループがないと、問題が発生します。つまり、JS が非同期 IO リクエストを開始すると、結果が返されるのを待っている間、次のコードがブロックされます。JS のメイン スレッドとレンダリング プロセスが相互にブロックされていることがわかっているため、ブラウザがフリーズします。この問題を解決するには?効果的な方法は、このセクションで説明することです事件循环
。
実際、事件循环就是用来做调度的,浏览器和NodeJS中的事件循坏就好像操作系统的调度器一样。
オペレーティング システムのスケジューラは、いつ、どのリソースを誰に割り当てるかを決定します。
ブラウザと NodeJS のイベント ループも基本的にスケジューリング用ですが、スケジューリングの対象は JS の実行になり、イベント ループは V8 がいつ、どのコードを実行するかを決定します (V8 は JS コードの解析と実行のみを担当し、他のすべては私が知りません)。ブラウザや NodeJS でイベントが発生してから、V8 でイベント リスナ関数が実行されるまでのすべての作業がイベント ループです。
イベントループが非同期にできる理由は、「setTimeout、setInterval」などの非同期で実行されるコードに遭遇すると、js がブラウザの WEB API を呼び出し、同時にブラウザがタイミングを開始したり、時間切れになったり、イベントが発生したりするためです。がトリガーされると、対応するコールバック関数がキューに入れられます。
メイン スレッドがコール スタック内のプログラムを「一気に」実行し、「呼吸」する必要がある場合、ブラウザはキューに処理する「メッセージ」があるかどうかを確認します。存在する場合は、メッセージ バインディングに対応するコールバック関数をスタックにプッシュします。
マイクロタスクとマクロタスク
マイクロタスク マイクロタスク
マイクロタスク (マイクロタスク) は、実際には 2 つの部分を含む一般的な用語です。
- process.nextTick() (ノード固有) 登録済みコールバック
- promise.then() によって登録されたコールバック
マクロ タスク マクロ タスク
- setTimeout によって登録されたコールバック
- setInterval によって登録されたコールバック
- setImmediate (ノード固有) 登録済みコールバック
- I/O 登録のコールバック
- スクリプト (コード全体)
- requestAnimationFrame (ブラウザーのみ)
- UI レンダリング (ブラウザーのみ)
ブラウザのイベントループ
ブラウザ イベント ループ フローチャート:
ブラウザ イベント ループの特定のプロセス:
-
js エンジンはすべてのコードを実行スタックに入れ、それらをポップアップして 1 つずつ実行します。これらのタスクには、同期タスクと非同期タスク (マクロ タスクまたはマイクロ タスク) があります。
-
スタック内のコードを実行するときにマクロ タスクが見つかった場合、それは処理するためにブラウザーの対応するスレッドに渡され、ブラウザー スレッドはマクロ タスクのメッセージをプッシュします (またはコールバック関数と呼ばれます)。適切な時間 (タイマーの最短遅延時間など) をマクロ タスク キューに追加します。マクロ タスク キュー内のタスクは、実行スタックが空の場合にのみ実行されます。
-
スタック内のコードを実行中にマイクロタスクが見つかった場合、マイクロタスク キューにプッシュされます. マクロタスク キューと同様に、マイクロタスク キュー内のタスクは実行スタックが空のときに実行されますが、マイクロタスクは常にその前に実行されますマクロタスク。
-
実行スタックが空になると、eventLoop は microtask キューに移動し、最初のタスクを順番にポップアップして実行スタックに入れ、実行します.実行中に別の microtask が生成された場合は、キューの最後にプッシュされ、マイクロタスク キューが空になるまでサイクルが続くようにします。
-
実行スタックとマイクロタスクキューの両方が空の場合、eventLoop はマクロタスクキューに転送し、キューの先頭にあるタスクを取り出して実行スタックに入れ、実行します。各サイクルで実行されるマクロタスクは 1 つだけであることに注意してください。
-
1~5の工程を繰り返す
-
...スタックとキューの両方が空になるまで、コードの実行は終了します。エンジンはスリープ状態になり、次のタスクが発生するまで待機します。
知らせ:
- 一度に実行されるマクロ タスクは 1 つだけで、マイクロ タスクは実行直後に実行されます。
- マイクロタスクは、マイクロタスク キューが空になるまで順次実行されます。
ノードイベントループ
ノード イベント フローの簡単な概略図
ノード内のマクロ タスク
- タイマー キュー
- IO コールバック キュー
- チェックキュー
- コールバック キューを閉じる
ノードのマイクロタスク
- Next Tick Queue: process.nextTick(callback) を配置するためのコールバック タスクです。
- その他のマイクロ キュー: Promise などの他のマイクロタスクを配置します。
nodejsのマクロタスクキュー
- timers phase: 初めてイベント ループに入ると、timer フェーズから開始されます。このステージでは、期限切れのタイマー コールバック (setTimeout と setInterval を含む) があるかどうかを判断します. ある場合は、期限切れのすべてのタイマー コールバックが実行されます. 実行後、対応するマイクロタスクがコールバックでトリガーされた場合、すべてのマイクロタスクが実行されます.次に、マイクロタスクの実行後に I/O コールバック ステージに入ります。
- I/O コールバック フェーズ: close イベントのコールバック、タイマーによって設定されたコールバック、および setImmediate() によって設定されたコールバック以外のコールバックを実行します。
- アイドル、準備段階: ノードによって内部的にのみ使用されます。
- ポーリング フェーズ: 新しい I/O イベントを取得すると、適切な条件下でノードがここでブロックされます。
- チェックフェーズ: setImmediate() によって設定されたコールバックを実行します。(setImmediate に関連するコールバックがあるかどうかを確認します。存在する場合は、すべてのコールバックを実行します。実行後、対応するマイクロタスクがコールバックでトリガーされた場合、すべてのマイクロタスクを実行し続けます。マイクロタスクを実行した後、入力しますクローズ コールバック ステージ)。
- クローズ コールバック フェーズ: socket.on('close', ...) などのいくつかのクローズ コールバックを実行します。
NodeのEventLoopの具体的な処理
- グローバル スクリプトの同期コードを実行する
- microtask microtask を実行し、最初にすべての Next Tick Queue 内のすべてのタスクを実行し、次に Other Microtask Queue 内のすべてのタスクを実行します。
- マクロタスク マクロタスクは、1 段目から 6 段目、ノード 11 以降までの合計 6 段で実行され、イベントループの各段では、マイクロタスクの実行順序が統一され、各段の後コールバックが呼び出されると、すべてのコールバックが実行されるまで待機する代わりに、対応するマイクロタスクが実行されます。
- 1 ~ 3 のプロセスを繰り返します。
イベントループのまとめ
- イベント ループは、ブラウザーと Node が JS コードを実行するためのコア メカニズムですが、ブラウザーと NodeJS のイベント ループの実装メカニズムは多少異なります。
- ブラウザのイベントループにはマクロキューとマイクロキューがあり、マイクロキューは実行過程でキューが空になるまで1つずつ実行されます.マクロキューはキューの先頭のタスクだけを取り出して実行のための実行スタック. 実行後, マイクロキューが実行されます. キューに入れられ, ループを形成します.
- Node 11 以降では、イベント ループの各段階で、マイクロタスクの実行順序が統一され、各コールバックが呼び出された後、対応するマイクロタスクが実行され、すべてのコールバックが実行されるまで実行されません。