目次- 序文
- ブラウザJS非同期実行の原理
- ブラウザのイベントループ
- 実行スタックとタスクキュー
- マクロタスクとマイクロタスク
- 非同期/待機実行順序
序文
イベント ループに精通し、ブラウザの動作メカニズムを理解することは、JavaScript の実行プロセスを理解し、動作上の問題をトラブルシューティングするのに非常に役立ちます。以下は、ブラウザ イベント ループのいくつかの原則と例の概要です。
ブラウザJS非同期実行の原理
- JS はシングルスレッドなので、一度に 1 つのことしか実行できません。では、なぜブラウザは非同期タスクを同時に実行できるのでしょうか?
- ブラウザはマルチスレッドなので、JS が非同期タスクを実行する必要がある場合、ブラウザは別のスレッドを開始してタスクを実行します。
- つまり、JS はシングルスレッドであり、JS コードを実行するスレッドは 1 つだけであり、それがブラウザによって提供される JS エンジン スレッド (メイン スレッド) であることを意味します。ブラウザにはタイマー スレッドや HTTP リクエスト スレッドもありますが、これらは主に JS コードの実行には使用されません。
- たとえば、メイン スレッドで AJAX リクエストを送信する必要がある場合、このタスクは別のブラウザー スレッド (HTTP リクエスト スレッド) に引き渡されて、実際にリクエストが送信されます。リクエストが戻った後、コールバックで実行する必要がある JS コールバックは、実行のために JS エンジン スレッドに引き渡されます。
- つまり、リクエストを送信するタスクを実際に実行するのはブラウザであり、JS は最終的なコールバック処理の実行のみを担当します。したがって、ここでの非同期性は JS 自体によって実装されているのではなく、実際にはブラウザによって提供される機能です。

ブラウザのイベントループ
実行スタックとタスクキュー
JS はコードを解析するときに、同期コードを特定の場所、つまり実行スタックに順番に並べ、その中の関数を 1 つずつ実行します。 非同期タスクが発生すると、他のスレッドに引き継がれて処理されます。現在の実行スタック内のすべての同期コードが実行された後、完了した非同期タスクのコールバックがキューから取り出され、実行スタックに追加されて実行が続行されます。 非同期タスクに遭遇すると、そのタスクは他のスレッドに引き渡される、などとなります。 他の非同期タスクが完了すると、コールバックはタスク キューに配置されて実行されます。 
マクロタスクとマイクロタスク
タスクの種類に応じて、マイクロタスクキューとマクロタスクキューに分けられます。 イベント ループ中、同期コードが実行された後、実行スタックはまずマイクロタスク キューに実行するタスクがあるかどうかを確認します。ない場合は、マクロタスク キューに実行するタスクがあるかどうかを確認します。 マイクロタスクは通常、現在のループで最初に実行されますが、マクロタスクは次のループまで待機します。したがって、マイクロタスクは通常、マクロタスクの前に実行され、マイクロタスク キューは 1 つだけですが、マクロタスク キューは複数存在する場合があります。さらに、クリックやキーボード イベントなどの一般的なイベントもマクロ タスクに属します。
一般的なマクロタスク: - タイムアウトを設定する()
- 間隔を設定する()
- UIインタラクションイベント
- 投稿メッセージ
- setImmediate() -- ノードJs
一般的なマイクロタスク: - promise.then()、promise.catch()
- 新しい MutaionObserver()
- process.nextTick() -- nodeJs
次の例:
console.log('同期コード1');
タイムアウトを設定する(() => {
コンソールログ('setTimeout')
}, 0)
新しいPromise((resolve) => {
console.log('同期コード2')
解決する()
}).then(() => {
コンソールログ('promise.then')
})
console.log('同期コード3');
// 最終出力: "同期コード 1"、"同期コード 2"、"同期コード 3"、"promise.then"、"setTimeout"
具体的な分析は以下のとおりです。 - setTimeout コールバックと promise.then は両方とも非同期で実行され、すべての同期コードの後に実行されます。
- promise.then は後に記述されますが、マイクロタスクであるため、その実行順序は setTimeout よりも優先されます。
- 新しい Promise は同期的に実行されますが、promise.then のコールバックは非同期的に実行されます。
注: ブラウザで setTimeout 遅延が 0 に設定されている場合、デフォルトで 4 ミリ秒に設定され、NodeJS では 1 ミリ秒になります。 マイクロタスクとマクロタスクの本質的な違い: - マクロタスクの特性: 実行してコールバックする必要がある明確な非同期タスクがあり、他の非同期スレッドがそれらをサポートする必要があります。
- Microtask の特性: 実行される明示的な非同期タスクはなく、コールバックのみであり、他の非同期スレッドのサポートは必要ありません。
非同期/待機実行順序特徴- 非同期宣言された関数は、関数の戻り値を単純にラップして、いずれにしても promise オブジェクトが返されるようにします (非 promise は promise{resolve} に変換されます)。
- await ステートメントは async 関数でのみ使用できます。
- 非同期関数の実行時に、await ステートメントに遭遇すると、まず「通常の実行ルール」に従って await 以降のコンテンツが実行されます (関数コンテンツの実行中に await ステートメントに再度遭遇すると、次に await ステートメントのコンテンツが実行されることに注意してください)。
- 実行後、すぐに async 関数から抜け出し、メインスレッドの他のコンテンツを実行します。メインスレッドの実行後、await に戻り、次のコンテンツの実行を継続します。
例
定数a = 非同期() => {
コンソールにログ出力します。
b() を待機します。
e() を待機します。
};
定数b = 非同期() => {
console.log("b 開始");
c() を待機します。
console.log("b 終了");
};
定数 c = 非同期() => {
console.log("c 開始");
d() を待機します。
console.log("c 終了");
};
定数d = 非同期() => {
コンソールにログ出力します。
};
定数e = 非同期() => {
コンソールにログ出力します。
};
コンソールにログ出力します。
();
コンソールログ('終了');
運用結果 
個人分析- 現在の同期環境では、まず console.log('start'); を実行して 'start' を出力します。
- 同期関数 a() に遭遇したら、a() を実行します。
- a() は sync/await 構造です。関数内で console.log("a") に遭遇すると、'a' が出力されます。await に遭遇すると、非同期関数である await b() が宣言されます。実行は関数 b() で行われます。 (同様の操作ですが、自分で考えました) await b() の後の内容をマイクロタスク キューにプッシュします。 [await b()] と書くこともできます。
- b() は sync/await 構造です。これを順番に実行すると、console.log("c start") に遭遇し、'c start' を出力します。await 宣言 await c() に遭遇すると、非同期関数となり、関数 c() を実行して実行されます。そして、await c() の後のコンテンツをマイクロタスク キューにプッシュします。これを [await c(), then await b()] と覚えることができます。
- c() は sync/await 構造です。これを順番に実行すると、console.log("b start") に遭遇し、'b start' が出力されます。非同期関数である await ステートメント await d() に遭遇すると、関数 d() が実行され始めます。そして、await d() の後のコンテンツをマイクロタスク キューにプッシュします。 [await d(), await c(), await b()] と覚えることができます。
- d() では、順次実行により console.log("") に遭遇し、'd' が出力され、d() 関数が終了します。
- これはd()を実行した後です。実行する非同期関数はありません。このとき同期環境に入り、a()以降の内容が実行されます。
- console.log("end") に遭遇すると、'end' が出力されます。このとき、同期環境のメインスレッドが実行され、マイクロタスクキューにマイクロタスクがあるかどうかがチェックされます。
- マイクロタスクキューには[after await d()、after await c()、after await b()]のマイクロタスクがあり、await d()以降の内容が実行されます。
- wait d() の後の内容は console.log("c end") となり、'c end' が出力されます。この時点でコンテンツが実行され、その後マイクロタスクキュー[after await c()、after await b()]からチェックされ、await c()後のコンテンツが実行されます。
- await c(), console.log("b end"); を実行した後の内容は、'b end' と出力されます。この時点でコンテンツが実行され、その後マイクロタスクキューから[after await b()]をチェックしてawait b()後のコンテンツを実行します。
- await d() の後の内容は await e() です。await 文に遭遇すると、e() が実行されます。そして、判断すると、await e() の後に実行するコードがないので、タスク キューに入る必要はありません。
- e() を実行し、次に console.log("e"); を順に実行して 'e' を出力します。関数はこの時点で終了します。
- マイクロタスクキュー[]にマイクロタスクが存在しないため、実行が終了します。同期環境に入ります。
これで、JavaScript のイベント実行メカニズムの詳細な理解に関するこの記事は終了です。JavaScript のイベント実行メカニズムに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:- いくつかの面接の質問を使ってJavaScriptの実行メカニズムを調べる
- JavaScript実行メカニズムの詳細な説明
- JavaScriptの実行メカニズムを徹底的に理解する
- JavaScript実行メカニズムの詳細な紹介
|