ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度

JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度

黄舟
黄舟オリジナル
2017-02-25 13:47:391251ブラウズ

1年前、「イベントループとは?」という記事を書きました。 」とイベントループについての私の理解を話しました。

先月、私はたまたまフィリップ・ロバーツのスピーチ「助けてください、イベントループにはまってしまいました」を目にしました。そのとき初めて、恥ずかしながら自分の理解が間違っていたことに気づきました。私は、JavaScript エンジンの内部動作を詳細に、完全に、そして正確に説明するためにこの質問を書き直すことにしました。以下は私のリライトです。

本文を入力する前にメッセージを挿入します。私の新しい本「ECMAScript 6 入門」が出版されました (著作権ページ、ページ 1、ページ 2 内) コート紙にフルカラーで印刷されており、インデックスも付いています (もちろん価格です)。類似の本より少し高価です)。プレビューして購入するには、ここをクリックしてください。

JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度

1. JavaScript はなぜシングルスレッドなのですか?

JavaScript 言語の主な特徴は、シングルスレッドであることです。つまり、一度に 1 つのことしか実行できません。では、なぜ JavaScript は複数のスレッドを持てないのでしょうか?これにより効率が向上します。

JavaScriptのシングルスレッドはその目的に関係しています。ブラウザーのスクリプト言語としての JavaScript の主な目的は、ユーザーと対話し、DOM を操作することです。これにより、シングルスレッドのみが可能であることが決まります。そうでない場合は、非常に複雑な同期の問題が発生します。たとえば、JavaScript に同時に 2 つのスレッドがあるとします。1 つのスレッドが特定の DOM ノードにコンテンツを追加し、もう 1 つのスレッドがそのノードを削除するとします。この場合、ブラウザーはどちらのスレッドを使用すればよいでしょうか。

したがって、複雑さを避けるために、JavaScript は誕生以来シングルスレッドであり、これがこの言語の中核機能となっており、今後も変更されることはありません。

マルチコア CPU の計算能力を活用するために、HTML5 は Web Worker 標準を提案しています。これにより、JavaScript スクリプトは複数のスレッドを作成できますが、子スレッドはメインスレッドによって完全に制御され、DOM を操作してはなりません。したがって、この新しい標準は JavaScript のシングルスレッドの性質を変更しません。

2. タスクキュー

シングルスレッドとは、すべてのタスクをキューに入れる必要があり、前のタスクが完了するまで次のタスクは実行されないことを意味します。前のタスクに時間がかかると、次のタスクも待たされることになります。

キューが大量の計算によるもので、CPU がビジー状態である場合は忘れてください。しかし、多くの場合、IO デバイス (入出力デバイス) が非常に遅いため (データを読み取る Ajax 操作など)、CPU はアイドル状態になっています。ネットワークから)続行する前に結果が出るまで待つ必要があります。

JavaScript 言語の設計者は、現時点では、CPU が IO デバイスを完全に無視し、待機中のタスクを一時停止し、後のタスクを最初に実行できることに気づきました。 IO デバイスが結果を返すまで待ってから、戻って中断されたタスクの実行を続行します。

したがって、JavaScriptには2つの実行方法があります。1つは、CPUが順番に実行し、前のタスクが終了してから次のタスクが実行されます。これを同期実行と呼びます。もう1つは、CPUが待ち時間の長いタスクをスキップする方法です。 . 後続のタスクを最初に処理します。これを非同期実行と呼びます。どの実行方法を使用するかを選択するのはプログラマの責任です。

具体的には、非同期実行の動作仕組みは以下の通りです。 (同期実行についても同様で、非同期タスクがなければ非同期実行とみなせるため)

(1) すべてのタスクはメインスレッド上で実行され、実行コンテキストスタックを形成します。

(2) メインスレッドの他に「タスクキュー」もあります。システムは非同期タスクを「タスクキュー」に入れ、後続のタスクを実行し続けます。

(3) 「実行スタック」内のすべてのタスクが実行されると、システムは「タスクキュー」を読み取ります。このとき、非同期タスクが待ち状態を終了していれば、「タスクキュー」から実行スタックに入り、実行を再開します。

(4) メインスレッドは上記の 3 番目のステップを繰り返し続けます。

下の図はメインスレッドとタスクキューの模式図です。

JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度

メインスレッドが空である限り、「タスクキュー」を読み込みます。これが JavaScript の実行メカニズムです。このプロセスは繰り返され続けます。

3. イベントとコールバック関数

「タスクキュー」は本質的にはイベントのキューです(メッセージのキューとも理解できます) IOデバイスがタスクを完了すると、イベントが「タスクキュー」に追加されます。 、関連する非同期タスクが「実行スタック」に入ることができることを示します。メインスレッドは「タスクキュー」を読み取ります。これは、その中のイベントを読み取ることを意味します。

「タスクキュー」内のイベントには、IOデバイスイベントに加えて、ユーザーが生成したイベント(マウスクリック、ページスクロールなど)も含まれます。コールバック関数が指定されている限り、これらのイベントは発生時に「タスクキュー」に入り、メインスレッドによる読み取りを待ちます。

いわゆる「コールバック関数」(コールバック)は、メインスレッドによってハングアップされるコードです。非同期タスクはコールバック関数を指定する必要があります。非同期タスクが「タスク キュー」から実行スタックに戻ると、コールバック関数が実行されます。

「タスクキュー」は先入れ先出しのデータ構造で、最初にランク付けされたイベントが最初にメインスレッドに返されます。メインスレッドの読み取りプロセスは基本的に自動であり、実行スタックがクリアされるとすぐに、「タスクキュー」の最初のイベントが自動的にメインスレッドに戻ります。ただし、後述する「タイマー」機能により、メインスレッドは実行時間をチェックする必要があり、特定のイベントは指定された時間にメインスレッドに返さなければなりません。

IV.イベントループ

メインスレッドは「タスクキュー」からイベントを読み込みます。このプロセスは周期的であるため、全体の動作メカニズムはイベントループとも呼ばれます。

イベント ループをよりよく理解するには、下の図をご覧ください (フィリップ ロバーツのスピーチ「助けて、イベント ループにはまってしまいました」から引用)。

Event Loop

上の図では、メインスレッドの実行中にヒープとスタックが生成され、スタック内のコードがさまざまな外部 API を呼び出し、さまざまなイベント (クリック、ロード、完了) を追加します。スタック内のコードが実行されている限り、メインスレッドは「タスクキュー」を読み取り、それらのイベントに対応するコールバック関数を順番に実行します。

実行スタック内のコードは、常に「タスクキュー」を読み取る前に実行されます。以下の例を見てください。

    var req = new XMLHttpRequest();
    req.open('GET', url);    
    req.onload = function (){};    
    req.onerror = function (){};    
    req.send();

上記のコードの req.send メソッドは、サーバーにデータを送信する Ajax 操作です。これは、システムがすべてのコードを読み終えるまで「タスク キュー」を読み取らないことを意味します。現在のスクリプトが実行されます。したがって、以下の記述に相当します。

    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};    
    req.onerror = function (){};

つまり、コールバック関数(onloadとonerror)を指定する部分がsend()メソッドの前であろうと後であろうと関係ありません、なぜならそれらは実行スタックの一部であり、システムは「タスクキュー」を読み込む前に必ず実行してください。

5. タイマー

非同期タスクを配置することに加えて、「タスクキュー」には時間指定されたイベントを配置する機能もあります。つまり、特定のコードがどのくらいの時間後に実行されるかを指定する機能です。これは「タイマー」関数と呼ばれ、定期的に実行されるコードです。

タイマー関数は主に setTimeout() と setInterval() の 2 つの関数によって完了します。それらの内部動作メカニズムはまったく同じです。違いは、前者で指定されたコードが 1 回実行されるのに対し、後者は繰り返し実行されることです。以下では主に setTimeout() について説明します。

setTimeout() は 2 つのパラメータを受け入れます。1 つ目はコールバック関数で、2 つ目は実行を遅らせるミリ秒数です。

console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

上記のコードの実行結果は、1、3、2になります。これは、setTimeout()が2行目の実行を1000ミリ秒後まで遅らせるためです。

setTimeout()の2番目のパラメータが0に設定されている場合、現在のコードが実行された(実行スタックがクリアされた)後、指定されたコールバック関数がすぐに(0ミリ秒間隔)実行されることを意味します。

setTimeout(function(){console.log(1);}, 0);
console.log(2);

上記のコードの実行結果は常に2, 1になります。これは、2行目が実行された後でのみ、システムが「タスクキュー」内のコールバック関数を実行するためです。

HTML5標準では、setTimeout()の第2パラメータの最小値(最短間隔)は4ミリ秒未満であってはならないと規定されており、この値より小さい場合は自動的に増加します。これより前の古いブラウザでは、最小間隔が 10 ミリ秒に設定されていました。

さらに、これらの DOM 変更 (特にページの再レンダリングを伴う変更) は通常、すぐには実行されず、16 ミリ秒ごとに実行されます。現時点では、setTimeout() よりも requestAnimFrame() を使用した方が効果が高くなります。

setTimeout() はイベントを「タスクキュー」に挿入するだけであることに注意してください。メインスレッドは、指定されたコールバック関数を実行する前に、現在のコード (実行スタック) の実行が終了するまで待機する必要があります。現在のコードに時間がかかる場合は、長時間かかる可能性があるため、setTimeout() で指定された時間にコールバック関数が実行されることを保証する方法はありません。

6. JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度のイベントループ

JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度もシングルスレッドのイベントループですが、ブラウザ環境とは動作の仕組みが異なります。

以下の図をご覧ください (著者 @BusyRich)。

JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度

上の図によると、JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度の動作仕組みは以下の通りです。

(1) V8 エンジンは JavaScript スクリプトを解析します。

(2) 解析されたコードは Node API を呼び出します。

(3) libuv ライブラリはノード API の実行を担当します。異なるタスクを異なるスレッドに割り当ててイベントループ(イベントループ)を形成し、タスクの実行結果を非同期でV8エンジンに返します。

(4) V8 エンジンは結果をユーザーに返します。

  JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度有一个process.nextTick()方法,可以将指定事件推迟到Event Loop的下一次执行,或者说放到"JavaScriptの動作仕組みを詳しく解説:イベントループの話をもう一度"的头部,也就是当前的执行栈清空之后立即执行。

function foo() {
    console.error(1);
}

process.nextTick(foo);
console.log(2);
// 2
// 1

  process.nextTick(foo)的作用,与setTimeout(foo, 0)很相似,但是执行效率高得多。

 以上就是JavaScript 运行机制详解:再谈Event Loop的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。