MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
JavaScriptの基礎 - イベントループのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
JavaScriptの基礎 - イベントループ
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == JavaScriptのイベントループは、シングルスレッドの実行環境において非同期処理を実現するための中核的な仕組みである。<br> <br> JavaScriptエンジンは任意の時点で1つのコードのみを実行するシングルスレッドモデルを採用している。<br> しかし、ネットワークリクエストやタイマ処理等の非同期操作が完了した時に、適切なタイミングでコールバック関数を実行する必要がある。<br> <br> イベントループは、コールスタック、マクロタスクキュー、マイクロタスクキューを監視・調整することにより、この要求に応える。<br> <br> * コールスタックの動作原理とシングルスレッドの特性 * イベントループの動作サイクル * マクロタスクとマイクロタスクの違いと優先順位 * 具体的なコード例による実行順序 * 非同期処理が"並列"ではない理由 * Web Workersによる真の並列処理との比較 * <code>requestAnimationFrame</code> と レンダリングの関係 <br> Promiseの詳細は[[JavaScriptの基礎 - Promise]]、async/awaitの詳細は[[JavaScriptの基礎 - async await]]、Fetch APIの詳細は[[JavaScriptの基礎 - Fetch API]]のページを参照すること。<br> <br><br> == JavaScriptの実行モデル == JavaScriptはシングルスレッドで動作し、コールスタックを使用して処理の実行順序を管理する。<br> <br> ==== シングルスレッド ==== JavaScriptエンジンは任意の時点で1つのステートメントのみを実行する設計となっている。<br> <br> 下表に、この設計の特性を示す。<br> <br> <center> {| class="wikitable" |+ シングルスレッドモデルの特性 ! 特性 !! 説明 |- | 予測可能性 || 任意の時点で実行されるコードが1つに限定されるため、<br>複数スレッドが同じデータを同時に変更するという問題が発生しない。 |- | スレッド管理の不要性 || マルチスレッド環境で必要となるロック機構や競合状態 (Race Condition) の<br>管理を考慮する必要がない。 |- | ブロッキングの問題 || 重い処理が実行中は他の処理が待機状態になるため、<br>UIの応答性に影響を与える可能性がある。 |} </center> <br> ==== コールスタック ==== コールスタック (Call Stack) は、現在実行中の関数を追跡するデータ構造である。<br> LIFO (Last In, First Out) 方式で動作し、後から追加されたスタックフレームが先に処理される。<br> <br> コールスタックの動作規則を以下に示す。<br> * 関数呼び出し時 *: 新しいスタックフレームが最上部に追加される。スタックフレームはローカル変数、パラメータ、実行コンテキスト情報を含む。 * 関数完了時 *: 最上部のスタックフレームが削除され、呼び出し元の関数に制御が戻る。 * コールスタック空の状態 *: 実行すべき同期コードがなくなった状態。イベントループがキューからタスクを取り出すタイミングとなる。 <br> コールスタックの遷移例を以下に示す。<br> <syntaxhighlight lang="javascript"> function a() { b(); } function b() { c(); } function c() { console.log('c'); } a(); </syntaxhighlight> <br> 上記コードの実行におけるスタック遷移を以下に示す。<br> <syntaxhighlight lang="bash"> ステップ1: a() 呼び出し -> スタック = [a] ステップ2: b() 呼び出し -> スタック = [a, b] ステップ3: c() 呼び出し -> スタック = [a, b, c] ステップ4: c() 完了 -> スタック = [a, b] ステップ5: b() 完了 -> スタック = [a] ステップ6: a() 完了 -> スタック = [] </syntaxhighlight> <br> 再帰呼び出しが無限に続く場合、スタックフレームが際限なく積み重なりスタックオーバーフローが発生する。<br> <br> <syntaxhighlight lang="javascript"> function infiniteRecursion() { infiniteRecursion(); } infiniteRecursion(); // RangeError: Maximum call stack size exceeded </syntaxhighlight> <br><br> == イベントループの仕組み == イベントループは、コールスタックとタスクキューを監視し、適切なタイミングでタスクを実行する役割を担う。<br> <br> ==== イベントループの動作サイクル ==== イベントループは、以下のサイクルを繰り返す。<br> <br> +-------------------------------------+ | 1. マクロタスク 1つを実行 | | (setTimeout, setInterval, I/O等) | +-------------------------------------+ | 2. マイクロタスク 全てを実行 | | (Promise.then, queueMicrotask等) | +-------------------------------------+ | 3. レンダリング (必要な場合) | +-------------------------------------+ | 4. 次のサイクルへ | +-------------------------------------+ <br> 下表に、各ステップの詳細を示す。<br> <br> <center> {| class="wikitable" |+ イベントループの各ステップの詳細 ! ステップ !! 説明 |- | ステップ1: マクロタスクの実行 || マクロタスクキューから1つのタスクを取り出して実行する。<br>コールスタックが空になるまで処理を続ける。 |- | ステップ2: マイクロタスクチェックポイント || マイクロタスクキュー内の全てのマイクロタスクを順番に実行する。<br>実行中に新しいマイクロタスクが追加された場合も全て処理される。 |- | ステップ3: レンダリング || ブラウザが必要と判断した場合、レイアウト計算、ペイント、合成を行う。 |- | ステップ4: 次のサイクルへ || マクロタスクキューに次のタスクがあれば、ステップ1に戻る。 |} </center> <br> ==== タスクキュー (マクロタスクキュー) ==== マクロタスクキューには、ブラウザや実行環境から発行される非同期タスクが格納される。<br> <br> 下表に、マクロタスクキューに格納されるタスクを示す。<br> <br> <center> {| class="wikitable" |+ マクロタスクキューに格納されるタスク ! タスク !! 説明 |- | <code>setTimeout()</code> / <code>setInterval()</code> || タイマコールバック<br>指定した遅延時間経過後にキューへ追加される。 |- | I/Oコールバック || ファイル読み込みやネットワークリクエスト完了時のコールバック |- | ユーザインタラクション || クリックやキー入力等のイベントハンドラ |- | <code>setImmediate()</code> || Node.js環境固有のタスク<br>現在の反復の終了後に実行される。 |} </center> <br> <u>重要な特性として、1ループサイクルにつき最大1つのマクロタスクのみ実行されることが挙げられる。</u><br> <br> ==== マイクロタスクキュー ==== マイクロタスクキューは、マクロタスクより高い優先度で処理されるタスクを格納する。<br> <br> 下表に、マイクロタスクキューに格納されるタスクを示す。<br> <br> <center> {| class="wikitable" |+ マイクロタスクキューに格納されるタスク ! タスク !! 説明 |- | <code>Promise.then()</code> / <code>Promise.catch()</code> / <code>Promise.finally()</code> || Promiseの状態変化に伴うコールバック |- | <code>queueMicrotask()</code> || 明示的にマイクロタスクを登録する関数 |- | <code>async</code>/<code>await</code> の <code>await</code> 以降 || <code>await</code> 式の後続処理はマイクロタスクとして実行される。 |- | <code>MutationObserver</code> || DOM変更を監視するコールバック |} </center> <br> マイクロタスクキューの特性を以下に示す。<br> * マクロタスク完了後、次のマクロタスクを実行する前に、キュー内の全てのマイクロタスクが実行される。 * 実行中に新しいマイクロタスクが追加されても、全て処理されてからマクロタスクに移行する。 * マイクロタスクはマクロタスクより常に優先される。 <br><br> == タスクとマイクロタスクの優先順位 == イベントループにおける実行優先順位を理解することは、非同期コードの動作を正確に把握するために不可欠である。<br> <br> ==== 実行順序のルール ==== 下表に、実行順序の優先度を示す。<br> <br> <center> {| class="wikitable" |+ 実行順序の優先度 ! 優先度 !! 種別 !! 代表例 |- | 1 (最高) || 同期コード || 通常のJavaScript文 |- | 2 || マイクロタスク || Promise.then, queueMicrotask, await以降 |- | 3 || レンダリング || 画面描画 (Webブラウザのみ) |- | 4 (最低) || マクロタスク || setTimeout, setInterval, I/Oコールバック |} </center> <br> 同期コードが全て完了した後、マイクロタスクキューが空になるまでマイクロタスクが実行される。<br> その後、必要であればレンダリングが行われ、次のマクロタスクが実行される。<br> <br> ==== setTimeout vs queueMicrotask vs Promise ==== 下表に、<code>setTimeout</code>、<code>queueMicrotask</code>、<code>Promise.resolve().then()</code> の違いを示す。<br> <br> <center> {| class="wikitable" |+ 非同期APIの比較 ! API !! キュー種別 !! エラーハンドリング !! 主な用途 |- | <u>setTimeout(fn, 0)</u> || マクロタスク || try / catchで捕捉可能 || 処理の遅延実行、UIブロッキング回避 |- | <u>Promise.resolve().then(fn)</u> || マイクロタスク || Promise chainで捕捉可能 || 非同期処理の後続処理 |- | <u>queueMicrotask(fn)</u> || マイクロタスク || 捕捉できない場合がある || シンプルなマイクロタスク登録 |} </center> <br> <u><code>queueMicrotask()</code> と <code>Promise.resolve().then()</code> は機能的にほぼ同等だが、エラーハンドリングの挙動に違いがある。</u><br> <u>エラーの捕捉が必要な場面では <code>Promise.resolve().then()</code> の使用を推奨する。</u><br> <br><br> == 実行順序の例 == ==== 基本的な実行順序 ==== * <code>console.log</code>、<code>setTimeout</code>、<code>Promise.then</code> を組み合わせた基本的な例 *: <syntaxhighlight lang="javascript"> console.log('開始'); setTimeout(() => { console.log('setTimeout'); }, 0); Promise.resolve() .then(() => console.log('Promise1')) .then(() => console.log('Promise2')); console.log('終了'); </syntaxhighlight> *: <br> * 出力結果 *: <syntaxhighlight lang="text"> 開始 終了 Promise1 Promise2 setTimeout </syntaxhighlight> <br> 下表に、実行順序の説明を示す。<br> <br> <center> {| class="wikitable" |+ 実行順序 ! ステップ !! 説明 |- | ステップ1 || <u>console.log('開始')</u> が同期コードとして実行される。 |- | ステップ2 || <u>setTimeout</u> のコールバックがマクロタスクキューに登録される。 |- | ステップ3 || <u>Promise.resolve().then()</u> の最初のコールバックがマイクロタスクキューに登録される。 |- | ステップ4 || <u>console.log('終了')</u> が同期コードとして実行される。 |- | ステップ5 || コールスタックが空になり、マイクロタスクチェックポイントが実行される。<br><code>Promise1</code> が実行され、続いて <code>Promise2</code> がマイクロタスクキューに追加される。 |- | ステップ6 || <code>Promise2</code> が実行される。 |- | ステップ7 || マイクロタスクキューが空になった後、マクロタスクの <code>setTimeout</code> コールバックが実行される。 |} </center> <br> ==== 複雑な実行順序 ==== * ネストした <code>setTimeout</code> と <code>Promise</code> を組み合わせた複雑な例 *: <syntaxhighlight lang="javascript"> console.log('1'); setTimeout(() => { console.log('2'); Promise.resolve().then(() => console.log('3')); }, 0); Promise.resolve() .then(() => { console.log('4'); setTimeout(() => console.log('5'), 0); }) .then(() => console.log('6')); console.log('7'); </syntaxhighlight> *: <br> * 出力結果 *: <syntaxhighlight lang="text"> 1 7 4 6 2 3 5 </syntaxhighlight> <br> 下表に、実行順序の説明を示す。<br> <br> <center> {| class="wikitable" |+ 実行順序の説明 ! フェーズ !! 説明 |- | 同期コード実行フェーズ || <u>1</u> が出力される。<br><u>setTimeout</u> のコールバックがマクロタスクキューに追加される。(タスクA)<br><u>Promise.then</u> のコールバックがマイクロタスクキューに追加される。(マイクロA)<br><u>7</u> が出力される。 |- | マイクロタスク実行フェーズ (1回目) || マイクロAが実行され、<u>4</u> が出力される。<br><u>setTimeout(() => console.log('5'), 0)</u> がマクロタスクキューに追加される。(タスクB)<br>次の <u>.then(() => console.log('6'))</u> がマイクロタスクキューに追加される。(マイクロB)<br>マイクロBが実行され、<u>6</u> が出力される。 |- | マクロタスク実行フェーズ (1回目) || タスクAが実行され、<u>2</u> が出力される。<br><u>Promise.resolve().then(() => console.log('3'))</u> がマイクロタスクキューに追加される。(マイクロC) |- | マイクロタスク実行フェーズ (2回目) || マイクロCが実行され、<u>3</u> が出力される。 |- | マクロタスク実行フェーズ (2回目) || タスクBが実行され、<u>5</u> が出力される。 |} </center> <br><br> == 非同期処理が"並列"ではない理由 == JavaScriptの非同期処理は並列処理ではなく並行処理である。<br> <br> この違いを理解することが重要である。<br> <br> ==== シングルスレッドと非同期の関係 ==== 3つの概念の違いを以下に示す。<br> <br> <center> {| class="wikitable" |+ 並行と並列の違い ! 概念 !! 説明 !! JavaScriptとの関係 |- | 非同期 (Asynchronous) || 処理の実行がブロッキングされない。 || JavaScriptの非同期モデルの基本 |- | 並行 (Concurrent) || 複数のタスクが交互に実行される。 || JavaScriptはこのモデルを採用 |- | 並列 (Parallel) || 複数の処理が同時に異なるCPUコアで実行される。 || JavaScriptのメインスレッドは非対応 |} </center> <br> JavaScriptの非同期処理は <u>並行</u> であり、<u>並列</u> ではない。<br> 任意の時点で実行されるのは1つのコードに限られるが、タスクを小さな単位に分けてイベントループが切り替えることにより、複数の処理を進行させる。<br> <br> 長時間かかる同期処理がUIをブロッキングする例を以下に示す。<br> <syntaxhighlight lang="javascript"> setTimeout(() => console.log('タイムアウト'), 100); // 重い処理 (UIをブロック) for (let i = 0; i < 1000000000; i++) {} // この処理が完了するまで、setTimeoutのコールバックは実行されない </syntaxhighlight> <br> 上記の例では、<u>setTimeout</u> で100[ms]後の実行を指定しても、その後のforループが完了するまでコールバックは実行されない。<br> <u>これは、コールスタックが空にならない限り、イベントループがタスクキューからタスクを取り出せないためである。</u><br> <br> ==== Web Workersによる真の並列処理 ==== 真の並列処理が必要な場合は、Web Workersを使用する。<br> Web WorkersはJavaScriptのメインスレッドとは独立したスレッドでコードを実行できる。<br> <br> Web Workersの使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> // main.js const worker = new Worker('worker.js'); worker.postMessage({ data: 1000000000 }); worker.onmessage = (event) => { console.log('結果:', event.data); }; </syntaxhighlight> <br> <syntaxhighlight lang="javascript"> // worker.js self.onmessage = (event) => { let result = 0; for (let i = 0; i < event.data; i++) result += i; self.postMessage(result); }; </syntaxhighlight> <br> 下表に、Web Workersの制限事項を示す。<br> <br> <center> {| class="wikitable" |+ Web Workersの制限事項 ! 制限事項 !! 説明 |- | DOM操作 || Web WorkersはDOMにアクセスできないため、UI操作はメインスレッドで行う必要がある。 |- | <code>window</code> / <code>document</code> へのアクセス || Webブラウザのグローバルオブジェクトにはアクセスできない。 |- | メインスレッドとの通信 || <code>postMessage()</code> を使用したメッセージパッシングで通信する。 |} </center> <br><br> == レンダリングとの関係 == ブラウザのレンダリングはイベントループのサイクルの中で実行される。<br> <br> レンダリングのタイミングを理解することは、スムーズなアニメーションやUI更新に重要である。<br> <br> ==== requestAnimationFrame ==== <code>requestAnimationFrame()</code> は、Webブラウザの次の描画フレーム直前に実行されるコールバックを登録する関数である。<br> <br> イベントループにおける実行順序を以下に示す。<br> <br> [マクロタスク] -> [マイクロタスク] -> [requestAnimationFrame] -> [レンダリング] <br> <code>requestAnimationFrame</code> と <code>setTimeout</code> の違いを以下に示す。<br> <br> <center> {| class="wikitable" |+ requestAnimationFrame と setTimeout の違い ! 項目 !! requestAnimationFrame !! setTimeout(fn, 0) |- | 実行タイミング || 次のフレーム描画直前 (通常 60[fps] = 約16.67[ms]) || 指定した遅延後 (精度は保証されない) |- | 描画との同期 || 常に同期する || 描画タイミングと合わない可能性がある |- | タブが非アクティブ時 || コールバックが停止する || 実行され続ける |- | バッテリー効率 || 効率的 || 比較的非効率 |} </center> <br> <code>requestAnimationFrame</code> を使用したアニメーションの基本的な例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> const element = document.getElementById('box'); let position = 0; function animate() { element.style.left = position + 'px'; position++; if (position < 300) { requestAnimationFrame(animate); } } requestAnimationFrame(animate); </syntaxhighlight> <br> <code>requestAnimationFrame</code> を使用することにより、Webブラウザの描画サイクルに合わせたスムーズなアニメーションを実現できる。<br> <code>setTimeout</code> を使用した場合と比較して、フレーム落ちや不必要な描画が発生しにくい。<br> <br><br> == 関連情報 == * [[JavaScriptの基礎 - Promise]] * [[JavaScriptの基礎 - async await]] * [[JavaScriptの基礎 - Fetch API]] * [[JavaScriptの基礎 - コールバック関数]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Web]]
JavaScriptの基礎 - イベントループ
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse