JavaScriptのイベントループの仕組みの分析

JavaScriptのイベントループの仕組みの分析

序文:

今回は主にJsのイベントループの仕組み、同期、非同期タスク、マクロタスク、マイクロタスクについての理解を整理します。今のところまだ多少の逸脱や誤りがある可能性が高いです。もしそうなら、私の間違いを訂正していただければ幸いです。

1. イベント ループとタスク キューの理由:

まず、JS はシングルスレッドなので、この設計は合理的です。DOM が一方側で削除され、もう一方に追加された場合、ブラウザはどのように処理するのでしょうか?

引用:

シングルスレッドとは、タスクがシリアルであり、次のタスクは前のタスクの実行を待つ必要があるため、待機時間が長くなる可能性があることを意味します。ただし、ajax ネットワーク リクエスト、setTimeout 時間遅延、DOM イベントによるユーザー操作などのタスクにより、これらのタスクは CPU を消費せず、リソースの無駄になります。そのため、非同期性が現れます。タスクを対応する非同期モジュールに割り当てて処理することで、メイン スレッドの効率が大幅に向上し、他の操作を並列処理できます。非同期処理が完了し、メイン スレッドがアイドル状態になると、メイン スレッドは対応するコールバックを読み取り、後続の操作を実行して、CPU の使用率を最大化します。このとき、同期実行と非同期実行の概念が現れます。同期実行とは、メイン スレッドがタスクを順番にシリアルに実行することを意味し、非同期実行とは、CPU が待機をスキップして後続のタスクを最初に処理することを意味します (CPU は、ネットワーク モジュール、タイマーなどと並列にタスクを実行します)。これにより、タスク キューとイベント ループが作成され、メイン スレッドと非同期モジュール間の作業が調整されます。

二、イベントループメカニズム:

図:

ここに画像の説明を挿入

まず、JS 実行コードの動作は主線程任務隊列に分かれています。任意の JS コードの実行は次のステップに分けられます。

ステップ 1: メイン スレッドは同期環境である JS コードを読み取り、対応するヒープと実行スタックを形成します。
ステップ 2: メイン スレッドが非同期操作に遭遇すると、その非同期操作を対応する API に渡して処理します。
ステップ 3: 非同期操作が完了したら、それをタスク キューにプッシュします。ステップ 4: メイン スレッドの実行後、タスク キューを照会し、タスクを取り出して、メイン スレッドにプッシュして処理します。ステップ 5: ステップ 2、3、4 を繰り返します。

一般的な非同期操作には、ajax リクエスト、setTimeout、同様の onclik イベントなどがあります。

三つ、タスクキュー:

同期タスクと非同期タスクはそれぞれ異なる実行環境に入ります。同期タスクはメインスレッド、つまりメイン実行スタックに入り、非同期タスクはタスクキューに入ります。

まず、名前の通りキューなので、 FIFO原則に従います。

上の図に示すように、複数のタスク キューがあり、その実行順序は次のとおりです。

同じタスク キューでは、タスクはキューの順序に従ってメイン スレッドによって取り除かれます。
異なるタスクキュー間には優先順位があり、優先順位の高いものが最初に取得されます(ユーザーI/Oなど)

3.1 タスクキューの種類:

タスクキューは宏任務(macrotask queue)微任務(microtask queue)に分かれています

マクロタスクには主に、スクリプト(コード全体)、setTimeout、setInterval、I/O、UIインタラクションイベント、setImmediate(Node.js環境)が含まれます。

マイクロタスクには主に、Promise、MutaionObserver、process.nextTick(Node.js 環境)が含まれます。

3.2 両者の違い:

microtask queue:

(1)ユニーク、イベントループ全体で1つだけ存在する。
(2)実行は同期的に行われます。同じイベントループ内のマイクロタスクはキューの順番に順次実行されます。

PS: したがって、マイクロタスクキューは同期実行環境を形成できる。

マクロタスクmacrotask queue :

(1)一意ではなく、一定の優先順位がある(ユーザーI/O部分の優先順位が高い)
(2)非同期実行:同じイベントループ内で実行されるイベントは1つだけである。

3.3より詳細なイベントループプロセス

  • 1. 2. 3. 上記と同じ
  • メインスレッドはタスクキューを照会し、マイクロタスクキューを実行し、それらを順番に実行して、すべての実行を完了します。
  • メイン スレッドはタスク キューを照会し、マクロ タスク キューを実行し、キューの最初のタスクを取得して実行します。
  • 手順 4 と 5 を繰り返します。

理解を深めるために簡単な例を見てみましょう。

console.log('1, time = ' + new Date().toString()) // 1. メインスレッドに入り、同期タスクを実行して、1 を出力します。
setTimeout(macroCallback, 0) // 2. マクロタスクキューに参加します // 7. このタイマーマクロタスクの実行を開始し、macroCallback を呼び出して 4 を出力します
new Promise(function (resolve, reject) { //3. マイクロタスクキューに参加 console.log('2, time = ' + new Date().toString()) //4. このマイクロタスク内の同期コードを実行し、2を出力する
  解決する()
  console.log('3, time = ' + new Date().toString()) //5. 出力3
}).then(microCallback) // 6. thenマイクロタスクを実行し、microCallbackを呼び出して5を出力する

//関数定義関数macroCallback() {
  console.log('4, 時間 = ' + 新しい Date().toString())
}

関数microCallback() {
  console.log('5, 時間 = ' + 新しい Date().toString())
}

実行結果:

画像の説明を追加してください

4、強力な非同期エキスパートプロセス.nextTick()

これを初めて見たとき、見覚えがあると思いました。よく考えてみると、以前 Vue プロジェクトでthis.$nextTick(callback)その時は、ページ上の要素が再レンダリングされた後にコールバック関数内のコードが実行される、と書いてありました。よくわからなかったので、とりあえず覚えておきます。

画像の説明を追加してください

4.1 process.nextTick() はいつ呼び出されますか?

特定のフェーズで process.nextTick() が呼び出されるたびに、イベント ループが続行される前に process.nextTick() に渡されるすべてのコールバックが解決されます。

イベントループでは、各ループ操作をtick呼びます。これを知っておくと、このメソッドが呼び出されるタイミングがわかりやすくなります。

イベント ループの理解を深めるために、他の人の例を借りてみましょう。

var flag = false // 1. 変数宣言 Promise.resolve().then(() => {
  // 2. then タスクをこのループのマイクロタスク キューに配布します console.log('then1') // 8. then マイクロタスクを実行し、then1 を出力します。この時点でフラグは true です flag = true
})
新しいPromise(resolve => {
  // 3. Promise 内の同期コードを実行する console.log('promise')
  解決する()
  setTimeout(() => { // 4. タイマーのタスクをマクロタスクキューに入れる console.log('timeout2') // 11. タイマーマクロタスクを実行する。ここでは待機時間 10 が指定されているため、別のタイマータスクの後に実行されます}, 10)
}).then(関数() {
  // 5. then タスクをこのループのマイクロタスク キューに配布します console.log('then2') // 9. then マイクロタスクを実行し、then2 を出力します。このティック ラウンドは終了します})
関数 f1(f) {
  // 1. 関数宣言 f()
}
関数 f2(f) {
  // 1. 関数宣言 setTimeout(f) // 7. `setTimeout` の `f` をマクロタスクキューに入れ、現在の `tick` ラウンドの実行を待機し、次のイベントループでそれを実行します}
f1(() => console.log('f is:', flag ? 'asynchronous' : 'synchronous')) // 6. `f is: synchronized` を出力します
f2(() => {
  console.log('timeout1,', 'f is:', flag ? 'asynchronous' : 'synchronous') // 10. タイマーマクロタスクを実行する})

console.log('このマクロタスクが実行されました') // 7. 印刷

実行結果:

画像の説明を追加してください

process.nextTick のコールバックは、現在のティックが実行された後、次のマクロ タスクが実行される前に呼び出されます。

公式の例:

バーを付ける;

// このメソッドは非同期シグネチャを使用しますが、実際にはコールバック関数を同期的に呼び出します someAsyncApiCall(callback) { callback(); }

// `someAsyncApiCall` が完了する前にコールバック関数が呼び出されますsomeAsyncApiCall(() => {
  // `someAsyncApiCall` が完了したため、bar には値が割り当てられません console.log('bar', bar); // undefined
});

バー = 1;

process.nextTickを使用します:

バーを付ける;

関数 someAsyncApiCall(コールバック) {
  process.nextTick(コールバック);
}

いくつかのAsyncApiCall(() => {
  console.log('bar', bar); // 1
});

バー = 1;

process.nextTickを使った別の例を見てみましょう。

console.log('1'); // 1. メインスレッドの実行スタックにプッシュして1を出力します

setTimeout(function () { //2. コールバック関数がマクロタスクキューに追加されます //7. 現在マイクロタスクキューは空なので、マクロタスクキューの最初の項目を取り出してこのタスクを実行します console.log('2'); // 出力 2
    process.nextTick(function () { // 16. 最後のループが終了し、次のマクロタスクが開始される前に呼び出され、出力3が返されます。
        コンソールログ('3'); 
    })
    新しいPromise(関数(resolve) {
    	//8. このプロミスの同期タスクを実行し、出力4、状態が解決される
        コンソールログ('4');
        解決する();
    }).then(function (){//9. 非同期メソッドthenを検出し、そのコールバック関数をマイクロタスクキューに追加します。console.log('5'); // 10. マイクロタスクキューの最初の項目(このthenのコールバック)を取り出し、それを実行して5を出力します。
    })
})

process.nextTick(function () { // 11. イベントループが終了したら、nextTick() のコールバックを実行して 6 を出力します。
    コンソールログ('6');
})
新しいPromise(関数(resolve) { 
	//3. Promiseの同期タスクを実行して7を出力すると、状態が解決済みに変わります。
    コンソールログ('7');
    解決する();
}).then(function () { //4. 非同期メソッドを検出し、そのコールバック関数をマイクロタスクキューに追加します console.log('8'); //6. メインスレッドが実行され、マイクロタスクキューの最初の項目が取り出され、そのコールバック関数が実行スタックにプッシュされ、8が出力されます
})

setTimeout(function () { //5. コールバック関数がマクロタスクキューに追加されます //12. この時点で、マイクロタスクキューは空であり、マクロタスクの実行が開始されます console.log('9'); // 出力 9
    process.nextTick(function () { // 17. この時点では、マイクロタスクとマクロタスクのキューは両方とも空なので、ループは自動的に終了し、このコールバックを実行して10を出力します。
        コンソールログ('10');
    })
    新しいPromise(関数(resolve) {
    	//13. この Promise の同期タスクを実行し、出力 11 と状態の変更を実行します。console.log('11');
        解決する();
    }).then(function (){//14. then非同期メソッドを検出し、それをマイクロタスクキューに追加します console.log('12');//15. マイクロタスクキューの最初の項目を取り出し、このthenマイクロタスクを実行して12を出力します
    })

})

実行結果:

画像の説明を追加してください

このプロセスは詳細に説明されています。

  • まず、メイン スレッドに入り、log が通常の関数であることを検出し、それを実行スタックにプッシュして、1 を出力します。
  • setTimeout が特殊な非同期メソッド (マクロタスク) であることが検出され、他のカーネル モジュールに処理が引き渡されます。setTimeout のコールバック関数は宏任務(macrotask)キューに配置されます。
  • promise オブジェクトと resolve メソッドが一般的なメソッドであることが検出され、その同期タスクが実行スタックにプッシュされ、7 が出力され、状態が解決済みに変更されます。
  • promise オブジェクトの then メソッドが非同期メソッドであることが検出され、他のカーネル モジュールに渡されて処理されます。コールバック関数は微任務(microtask)キューに配置されます。
  • 別の setTimeout が特別な非同期メソッドとして検出され、そのコールバック関数が宏任務(macrotask)キューに配置されます。
  • この時点で、メイン スレッドは空であり、タスク キューから取得を開始し、マイクロタスク キューの最初の項目 (最初のプロミスの then メソッドのコールバック) を取得して実行し、8 を出力します。
  • この時点でマイクロタスク キューが空であることを確認し、マクロタスク キューの最初の項目 (最初の setTimeOut) を取り出し、そのコールバック関数を実行して 2 を出力します。
  • コールバックでは、Promise に遭遇し、同期タスクを実行して 4 を出力し、状態が変化します。
  • 次に、上記のように検出して、マイクロタスク キューに追加します。
  • マイクロタスク キューの最初の項目を取り出してメイン スレッド (現在の時点) で実行し、5 を出力します。
  • このループが終了し、次のマクロ タスクが開始する前に、最初の process.nextTick() のコールバックが呼び出され、出力は 6 になります。
  • 次のマクロ タスクを開始し、マクロ タスク キューの最初の項目 (2 番目の setTimeout のコールバック) を取り出して実行スタックにプッシュし、9 を出力します。
  • 次に、promise オブジェクトの同期タスクを実行スタックにプッシュし、出力 11 を出力して、状態を解決済みに変更します。
  • このとき、非同期の then メソッドが再度検出され、そのコールバックが上記のようにマイクロタスク キューに追加されます。
  • マイクロタスク キューの最初の項目 (現在の then コールバック) を取り出し、12 を出力します。
  • このループは終了し、次のマクロ タスクが開始される前に実行されます。process.nextTick() のコールバックは 3 を出力します。
  • この時点で、タスク キューとメイン スレッドが空であることがわかり、イベント ループは自動的に終了し、最後の process.nextTick() コールバックが実行され、10 が出力されます。

仕上げる!インスピレーションが湧いたらすぐに書き留めて、後で問題がないか確認します。間違いがあればご指摘いただければ幸いです。

簡単な例を分析してみましょう。

コンソールログ('0');
タイムアウトを設定する(() => {
    コンソールログ('1');
    新しいPromise(function(resolve) {
        コンソールログ('2');
        解決する();
    })。そして()=>{
        コンソールログ('3');
    })
    新しいPromise(resolve => {
        コンソールログ('4');
        (i=0;i<9;i++) の場合{
            i == 7 && 解決();
        }
        コンソールログ('5');
    }).then(() => {
        コンソールログ('6');
    })
})
  • メインスレッドに入り、ログが通常の関数であることを検出し、それを実行スタックにプッシュして、0 を出力します。
  • setTimeOut は特別な非同期メソッドであり、処理のために他のモジュールに渡され、そのコールバック関数がマクロタスク キューに追加されることが検出されます。
  • この時点ではメイン スレッドにタスクがないので、タスク キューからタスクの取得を開始します。
  • タスク キューが空であることがわかった場合、マクロ タスク キューの最初の項目 (現在のタイマーのコールバック関数) が取り出されます。
  • 同期タスクを実行して 1 を出力します。
  • promise とその解決メソッドが一般的なメソッドであることを検出し、それらを実行スタックにプッシュして 2 を出力し、状態を解決に変更します。
  • このプロミスの then メソッドが非同期メソッドであることを検出し、そのコールバック関数をマイクロタスク キューに追加します。
  • 次に別のプロミスが検出され、同期タスクが実行され、4 と 5 が出力され、状態が解決済みに変わります。
  • 次に、非同期メソッドをマイクロタスク キューに追加します。
  • マイクロタスク キューの最初の項目、つまり最初の promise の then を実行し、3 を出力します。
  • 次に、タスク キューの最初の項目 (2 番目の Promise の then) を取り出し、6 を出力します。
  • この時点で、メイン スレッドとタスク キューは両方とも空になり、実行は完了します。

コード実行結果:

画像の説明を追加してください

画像の説明を追加してください

JavaScript のイベント ループ メカニズムの分析に関するこの記事はこれで終わりです。JavaScript のイベント ループ メカニズムに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JSのイベントループの仕組みを説明する記事
  • JavaScriptのイベントループの仕組みについて簡単に説明します
  • jsのイベントループ機構の解析
  • JSブラウザイベントループの仕組みの詳しい説明
  • JavaScriptイベントループの仕組みの詳しい説明
  • js イベントループメカニズムの分析例
  • JS のイベントループメカニズムの詳細な例

<<:  HTML ハイパーリンク スタイル (4 つの異なる状態) の設定例

>>:  CSS における要素の表示モード

推薦する

Vue.js ソースコード解析のカスタム手順の詳細な説明

序文コア機能のデフォルトの組み込みディレクティブ (v-model および v-show) に加えて...

MySQL 更新セットとの違い

目次問題の説明原因分析解決問題の説明最近、奇妙な問い合わせを受けました。更新ステートメントはエラーな...

便利でシンプルなMySQL関数10個

関数0. 現在の時刻を表示するコマンド: select now()。機能: 現在の時刻を表示します。...

iframe なしの div ネスト HTML

最近、宿題をしているときに、iframe を使用せずにページをネストする必要があったため、jquer...

mysql-8.0.19-winx64 をインストールしてログインするための初心者向けチュートリアル (初心者必読)

目次1. インストールパッケージ(64ビット)をダウンロードする2. MySQLデータベースをインス...

Tomcatにデプロイされたアプリケーションがフロントエンドページにアクセスできない問題について

最近、実践的なトレーニング プロジェクトを実行する際に ssm フレームワークを使用しました。プロジ...

MySQL の日付フォーマットと複雑な日付範囲クエリ

目次序文クエリの使用シナリオ例時間間隔クエリクエリ日付と今日の時間の比較データ一般的なサイクルタイム...

JSインターセプト文字列の3つの方法の詳細な説明

JS には、文字列をインターセプトするための 3 つのメソッド、 slice() 、 substri...

MySQL パーティションテーブルの制限と制約の詳細な説明

ビルドを無効にするパーティション式では、次の構成はサポートされません。ストアドプロシージャ、ストアド...

MySql への新しいユーザーの追加、ユーザー用のデータベースの作成、ユーザーへの権限の割り当ての概要

1. 新しいユーザーを追加するローカルIPアクセスのみを許可する '123456' ...

Vue プロジェクトのパッケージ化、マージ、圧縮により、Web ページの応答速度を最適化します。

目次序文1. リクエスト内容が大きすぎる解決: CDN の紹介リクエストリソースを圧縮する1. HT...

Selenium+testng を使用して Docker で Web 自動化を実現する方法

序文長い間さまざまな資料を読んで、ついに selenium+testng のパラメータ化の問題を解決...

MySQL ALTERコマンドの知識ポイントのまとめ

テーブル名を変更したり、テーブル フィールドを変更したりする必要がある場合は、 MySQL ALTE...

MySQL スケジュールされたデータベース バックアップ操作の例

この記事では、MySQL のスケジュールされたデータベース バックアップ操作の例について説明します。...

Linux システム (CentOS7 インストール) に JDK8 をインストールするための詳細なチュートリアル

JDKのインストールシステムのインストールについてはあまり詳しく説明しません。インストール手順はオン...