並行モデルとイベントループ
JavaScript は、"event loop"に基づく同時実行モデルを持ちます。このモデルは C 言語や Java のような他の言語のモデルとかなり異なっています。
ランタイムの概要
後続のセクションでは、理論モデルを説明します。現代の JavaScript エンジンは、記載されたセマンティクスに従って実装され、また高度に最適化されています。
視覚表示
スタック
関数呼び出しはフレームのスタックを形成します。
function foo(b) {
var a = 10;
return a + b + 11;
}
function bar(x) {
var y = 3;
return foo(x * y);
}
console.log(bar(7)); // returns 42
bar
を呼び出すと、bar
の引数とローカル変数を含んだ最初のフレームが生成されます。bar
がfoo
を呼び出すと、foo
の引数とローカル変数を含んだ2番目のフレームが生成され、最初のフレームの上にプッシュされます。foo
から返ると、先頭のフレーム要素はスタックからポップされます(bar
のコールフレームのみが残ります)。bar
から返るときスタックは空になります。
ヒープ
オブジェクトはヒープに割り当てられています。ヒープは、メモリの大規模で大部分は構造化されていない領域を意味する名前です。
キュー
JavaScript ランタイムはメッセージキューを含んでいます。メッセージキューは、処理されるメッセージのリストです。各メッセージに関数を関連付けられています。スタックが空のとき、メッセージがキューから取り出され、処理されます。その処理は、関連する関数の呼び出し(と最初のスタックフレームの作成)で構成されています。スタックが再度空になると、メッセージ処理は終了します。
イベントループ
event loop
という名前は、それが一般的にどのように実装されたかに従って付けられました。これは通常、次のものに似ています。
while (queue.waitForMessage()) {
queue.processNextMessage();
}
queue.waitForMessage
はもしその時点でメッセージが存在しないのであれば、同期的にメッセージが到着するのを待ちます。
"Run-to-completion"
その他のメッセージが処理される前に、各メッセージは完全に処理されています。 関数が実行されるたびに、それが横取りすることはできず、他のコードが実行される前に、完全に実行されます(および関数が操作するデータを変更することができる)という事実を含め、プログラムについて推論するときにいくつかの素晴らしい特性を提供しています。例えば、これは C とは異なります。というのは、関数はスレッドで実行されている場合、それは別のスレッドでいくつかの他のコードを実行するには、任意の時点で停止することができます。
このモデルの欠点は、メッセージが完了するまでに時間がかかりすぎる場合は、Web アプリケーションはクリックやスクロールのようなユーザインタラクションを処理することができないことです。ブラウザは"スクリプトは実行に非常に時間がかかる"ダイアログを用いてこれを軽減します。追従するお勧めは、メッセージを短い処理にし、可能な場合には、いくつかのメッセージに一つのメッセージを切り縮めることです。
メッセージの追加
Web ブラウザでは、メッセージは、イベントが発生し、それに接続されているイベントリスナーがある任意の時間に追加されます。イベントリスナーがない場合、イベントは失われます。だから、他のイベントと同様に、クリックイベントハンドラを持つ要素をクリックすると、メッセージが追加されます。
setTimeout
を呼び出すと、2 番目の引数として渡された時間が経過した後、メッセージがキューに追加されます。キューに他のメッセージがない場合、メッセージはすぐに処理されます。しかしながら、メッセージがある場合、setTimeout
メッセージは他のメッセージを処理するために待機する必要があります。そのため第二引数は、保証時間ではなく、最小の時間を示しています。
一緒に通信するいくつかのランタイム
ウェブワーカーやクロスオリジンの iframe は、独自のスタック、ヒープ、およびメッセージキューがあります。二つの異なるランタイムのみpostMessage
メソッドによって送信メッセージを介して通信することができます。他のランタイムがmessage
イベントをリッスンする場合、このメソッドは他のランタイムにメッセージを追加します。
ブロッキング不可
イベントループモデルの非常に興味深い特性は、他の多くの言語とは異なり、JavaScript は決してブロックしないことです。I/O の取り扱いは、通常、イベントとコールバックを介して行われます。そのため、アプリケーションはIndexedDBのクエリやXHRリクエストが返るのを待っている時も、ユーザ入力のような他のことを処理することができます。
alert
か同期 XHR のようにレガシーな例外が存在しますが、それらを避けることは良い慣習とされています。例外に対する例外は存在することに気をつけてください(と言っても、たいていは他の何かというよりはむしろ実装のバグですが)。