本格的な運用コードを作成するには C++ や C などの言語を深く理解する必要があるかもしれませんが、JavaScript は多くの場合、その言語で何ができるのかについての基本的な理解だけで作成できます。
関数にコールバックを渡す、非同期コードを記述するなどの概念は、多くの場合実装がそれほど難しくないため、ほとんどの JavaScript 開発者は内部で何が起こっているかをあまり気にしません。彼らは、言語によって深く抽象化された複雑さを理解することに関心がありません。
JavaScript 開発者として、内部で実際に何が起こっているのか、抽象化されたこれらの複雑さのほとんどが実際にどのように機能するのかを理解することがますます重要になっています。これは、より多くの情報に基づいた意思決定を行うのに役立ち、結果的にコードのパフォーマンスを大幅に向上させることができます。
この記事では、JavaScript における非常に重要だがほとんど理解されていない概念や用語の 1 つに焦点を当てます。イベントループ!
JavaScript では非同期コードの記述を避けることはできませんが、コードが非同期で実行されるということは実際にはなぜ意味があるのでしょうか?つまり、イベントループ
イベント ループがどのように機能するかを理解する前に、まず JavaScript 自体が何であり、どのように機能するかを理解する必要があります。
JavaScriptとは何ですか?
先に進む前に、非常に基本的なところに立ち返ってみたいと思います。 JavaScript とは実際何ですか? JavaScript を次のように定義できます。
JavaScript は、高レベルのインタープリタ型、シングルスレッド、ノンブロッキング、非同期、同時実行言語です。
ちょっと待って、これは何ですか?本っぽい定義? 🤔
分解してみましょう!
この記事に関するキーワードは、 シングルスレッド、ノンブロッキング 、 同時実行 、非同期です。
シングルスレッド
実行スレッドは、スケジューラによって独立して管理できる、プログラムされた命令の最小のシーケンスです。プログラミング言語はシングルスレッドであるため、一度に 1 つのタスクまたは操作しか実行できません。これは、スレッドが中断または停止されることなく、プロセス全体を最初から最後まで実行することを意味します。
マルチスレッド言語とは異なり、複数のプロセスを互いにブロックすることなく複数のスレッドで同時に実行できます。
JavaScript をシングルスレッドでありながら同時にノンブロッキングにするにはどうすればよいでしょうか?
しかし、ブロックとは何を意味するのでしょうか?
ノンブロッキング
ブロッキングには単一の定義はありません。これは単に、スレッド上で実行速度が遅いことを意味します。したがって、ノンブロッキングとは、スレッド上で遅くないことを意味します。
しかし、待ってください、JavaScript は単一のスレッドで実行されると言いましたか?また、ノンブロッキングとも言いましたが、これはタスクがコールスタック上で迅速に実行されることを意味しますか?でもどうやって?タイマーを実行する場合はどうでしょうか?ループ?
リラックス!少しすれば分かるでしょう 😉。
同時
同時実行とは、コードが複数のスレッドによって同時に実行されていることを意味します。
さて、状況が非常に奇妙になってきました。JavaScript をシングルスレッドで同時に実行するにはどうすればよいでしょうか?つまり、コードを複数のスレッドで実行しますか?
非同期
非同期プログラミングとは、コードがイベント ループ内で実行されることを意味します。ブロック操作があると、イベントが開始されます。ブロッキング コードは、メインの実行スレッドをブロックすることなく実行を続けます。ブロッキング コードの実行が終了すると、ブロッキング操作の結果がキューに入れられ、スタックにプッシュされます。
しかし、JavaScript には単一のスレッドがあるのでしょうか?では、スレッド内の他のコードを実行させながら、このブロック コードを実行するものは何でしょうか?
先に進む前に、上記の内容を復習しましょう。
- JavaScript はシングルスレッドです
- JavaScript はノンブロッキングです。つまり、遅いプロセスがその実行をブロックしません。
- JavaScript は同時実行です。つまり、コードを複数のスレッドで同時に実行します。
- JavaScript は非同期です。つまり、別の場所でブロック コードを実行します。
しかし、上記は正確に合計されません。シングルスレッド言語はどのようにしてノンブロッキング、同時実行、非同期になれるのでしょうか?
もう少し詳しく、JavaScript ランタイム エンジン V8 について見てみましょう。おそらく、私たちが気づいていない隠れたスレッドがあるかもしれません。
V8エンジン
V8 エンジンは、Google が C++ で記述した JavaScript 用の高性能のオープンソース Web アセンブリ ランタイム エンジンです。ほとんどのブラウザーは V8 エンジンを使用して JavaScript を実行し、人気のある Node JS ランタイム環境でも V8 エンジンを使用しています。
簡単に言うと、V8 は C++ プログラムであり、JavaScript コードを受け取り、コンパイルして実行します。
V8 は 2 つの主要な機能を果たします。
- ヒープメモリの割り当て
- コールスタック実行コンテキスト
残念ながら、私たちの疑念は間違っていました。 V8 には呼び出しスタックが 1 つだけあり、呼び出しスタックをスレッドと考えてください。
1 つのスレッド === 1 つのコール スタック === 一度に 1 つの実行。
V8 にはコール スタックが 1 つしかないため、メインの実行スレッドをブロックせずに JavaScript を同時に非同期に実行するにはどうすればよいでしょうか?
シンプルだが一般的な非同期コードを書いて一緒に分析して調べてみましょう。
JavaScript は各コードを 1 行ずつ実行します (シングルスレッド)。予想どおり、ここでは最初の行がコンソールに出力されますが、最後の行がタイムアウト コードの前に出力されるのはなぜですか?実行プロセスが最後の行の実行に進む前に、タイムアウト コード (ブロック) を待機しないのはなぜですか?
スレッドはどの時点でも 1 つのタスクしか実行できないと確信しているため、他のスレッドがタイムアウトの実行に役立っているようです。
しばらくV8 ソース コードを覗いてみましょう。
待って…何??!!! V8 にはタイマー機能がなく、DOM もありません。イベントはありませんか? AJAX はありませんか?…。やったー!!
イベント、DOM、タイマーなどは JavaScript のコア実装の一部ではありません。JavaScript は Ecma スクリプト仕様に厳密に準拠しており、そのさまざまなバージョンは Ecma スクリプト仕様 (ES X) に従って参照されることがよくあります。
実行ワークフロー
イベント、タイマー、Ajax リクエストはすべてブラウザーによってクライアント側で提供され、多くの場合 Web API と呼ばれます。これらは、シングルスレッド JavaScript のノンブロッキング、同時実行、非同期を可能にするものです。しかし、どうやって?
JavaScript プログラムの実行ワークフローには、コール スタック、Web API、およびタスク キューという 3 つの主要なセクションがあります。
呼び出しスタック
スタックは、最後に追加された要素が常にスタックから最初に削除されるデータ構造です。最後に追加された最初のプレートのみが最初に削除できるプレートのスタックと考えることができます。呼び出しスタックは、タスクまたはコードがそれに応じて実行されるスタック データ構造にすぎません。
以下の例を考えてみましょう。
関数printSquare()
を呼び出すと、関数は呼び出しスタックにプッシュされ、 printSquare()
関数は square() 関数を呼び出します。 square()
関数はスタックにプッシュされ、 multiply()
関数も呼び出します。乗算関数はスタックにプッシュされます。 multiply 関数は返され、最後にスタックにプッシュされたものであるため、最初に解決されてスタックから削除され、続いてsquare()
関数、次にprintSquare()
関数が続きます。
ウェブ API
ここでは、メイン実行スレッドを「ブロック」しないように、V8 エンジンによって処理されないコードが実行されます。呼び出しスタックが Web API 関数に遭遇すると、プロセスはすぐに Web API に渡され、そこで実行され、実行中に呼び出しスタックが解放されて他の操作を実行できるようになります。
上記のsetTimeout
例に戻りましょう。
コードを実行すると、最初の console.log 行がスタックにプッシュされ、タイムアウトになるとほぼ即座に出力が得られます。タイマーはブラウザによって処理され、V8 のコア実装の一部ではありません。プッシュされます。代わりに Web API にアクセスし、スタックを解放して他の操作を実行できるようにします。
タイムアウトがまだ実行されている間、スタックは次のアクション行に進み、最後の console.log を実行します。これは、タイマー出力の前に出力される理由を説明しています。タイマーが完了すると、何かが起こります。 console.log にログインすると、タイマーが魔法のようにコール スタックに再び表示されます。
どうやって?
イベントループ
イベント ループについて説明する前に、まずタスク キューの機能について説明します。
タイムアウトの例に戻ると、Web API はタスクの実行を完了すると、タスクを自動的に呼び出しスタックにプッシュし戻すだけではありません。タスクキューに入れられます。
キューは先入れ先出しの原則に基づいて動作するデータ構造であるため、タスクがキューに押し込まれると、同じ順序で取り出されます。 Web API によって実行され、タスク キューにプッシュされたタスクは、コール スタックに戻って結果を出力します。
ちょっと待って。イベントループとは一体何ですか?
イベント ループは、タスク キューからコール スタックにコールバックをプッシュする前に、コール スタックがクリアされるのを待つプロセスです。スタックがクリアされると、イベント ループがトリガーされ、タスク キューで利用可能なコールバックがないかチェックされます。存在する場合は、それを呼び出しスタックにプッシュし、呼び出しスタックが再び空になるのを待ち、同じプロセスを繰り返します。
上の図は、イベント ループとタスク キュー間の基本的なワークフローを示しています。
結論
これは非常に基本的な入門ですが、JavaScript の非同期プログラミングの概念は、内部で何が起こっているのか、JavaScript が単一のスレッドでどのように同時および非同期で実行できるのかを明確に理解するのに十分な洞察を提供します。
JavaScript は常にオンデマンドです。学習に興味がある場合は、このUdemy コースをチェックすることをお勧めします。