JavaScript アンダー ザ フード: イベント ループ

Dec 01 2022
JavaScript の機能をゼロから調べてマスターしましょう 未定義のエラーに直面したり、変数のスコープを特定するのに苦労したことはありませんか? コードがどのように動作するかを知らずにエラーをデバッグするのは、本当に時間がかかります。このブログでは、実行コンテキストに関するイベント ループ、コール スタック、コールバック キューなどの高度な概念が実際にどのように機能するかを説明します。

ゼロから機能を調べてJavaScriptをマスターしよう

UnsplashのMarc Stによる写真

未定義のエラーに直面したり、変数のスコープを特定するのに苦労したことはありませんか?

コードがどのように動作するかを知らずにエラーをデバッグするのは、本当に時間がかかります。

このブログでは、 、、 などの高度な概念が実際にどのようevent loopに機能するかを説明します。execution contextcall stackcallback queue

免責事項 — 概念は信じられないほど知識集約的で相互に関連しているため、まばたきさえしないでください。

JavaScript エンジン

Google の V8 エンジンは、JavaScript エンジンのよく知られた例です。たとえば、Chrome と Node.js はどちらも V8 エンジンを採用しています。基本的に、V8 エンジンは 2 つの部分で構成されています —

  1. Call Stack :すべてのコードはその中で実行されます。スタック データ構造のように機能します。つまり、LIFO (後入れ先出し) の概念に従います。
  2. メモリ ヒープ:メモリ割り当てが発生する場所。ヒープ データ構造とは異なり、これは単なるメモリです。

グローバル実行コンテキスト (GEC) は、スクリプト ファイルを受信するたびに JavaScript エンジンによって作成されるデフォルトの実行コンテキストです。

関数内にないすべての JavaScript コードは で実行されGECます。次の手順は、次の場所で実行されますGEC

  • グローバル スケールですべての変数と関数を格納するためのメモリ空間を構築する
  • グローバル オブジェクトを生成します。
  • キーワードを生成するthis

コードが実行される場所に応じて、 の場所が決まりthisます。たとえば、Node.js では個別のグローバル オブジェクトを指しますが、ブラウザーではwindowオブジェクトを指します。

ブラウザ コンソール

呼び出しスタック (または関数実行スタック)

single-threadedすでに聞いたとおり、JavaScript はです。しかし、それは実際にはどういう意味ですか?

これは、JavaScript エンジンにcall stackまたはが 1 つだけ含まれていることを意味しますfunction execution stack

  • 私たちが知っているように、コンパイラが最初にコードを調査するときはいつでも、JS エンジンはコンパイラによってGlobal Execution Contextまたはを作成するように求められ、 .GECCall Stack
  • コード全体が で 1 つずつ実行されGlobal Execution Context、関数定義または変数宣言用にメモリが割り当てられ、そこに格納されます。
  • ただし、関数呼び出しが見つかると、関数のコードを実行するためにFunctional Execution ContextorFECが作成され、call stack.
  • インタープリターは、関数がcall stack終了するたびに から関数を削除します。関数は、そのスコープまたは return ステートメントの終わりに達したときに終了します。
  • 最後に、コード全体を実行するとGECCall Stack.

心配しないで、例を挙げて説明しましょう。

function f1{
  console.log('f1');
}

f1();
console.log('end');

ステップ 2: —この例では、最初の行が実行されますf1。メモリが割り当てられ、f1その定義が保存されます。

ステップ 3: — 2 行目で、関数が呼び出されます。この関数呼び出しでは、Function Execution Context orFECが作成され、 の上に格納されCall Stackます。

ステップ 4: —これで、全体f1()が 1 行ずつ実行され、実行が終了すると から削除されますCall Stack

ステップ 5: —次に、最後の行console.log('end')が実行され、コンソールに「end」が出力されます。最後に、コード全体を実行するGlobal Execution Contextと、Call Stack.

JS は非同期タスクをどのように管理しますか?

JavaScript は、よく知られているように、同期型のシングル スレッド言語 (一度に 1 つのタスク) であり、call stack内部にあるものは何でもすぐに実行されるシングル スレッドです。

しかし、5 秒後に何かを実行する必要がある場合はどうなるでしょうか。の中でそれを行うことはできますcall stackか?

いいえ、できません。call stackタイマーがないからです。しかし、どうすればそれができるのでしょうか?

ここで、JavaScript ランタイムの出番です。

JavaScript ランタイム環境

私が今あなたに言うと、悲嘆に暮れる可能性があります.JavaScriptsetTimeout()の一部ではありませんが、console.log()DOMイベントはすべて、 JavaScriptエンジンにアクセスしてGlobal Execution Context(GEC)ですべてのプロパティを使用できるWeb APIの一部です。グローバル オブジェクト。これらのWeb APIは非同期と呼ばれます。window

コールバック キューとは

「コールバック キュー」または「タスク キュー」と呼ばれるタスクのキューは、コール スタックの現在の役割が完了した後に実行されるものです。Web API に登録されているタスクは、Web API からコールバック キューに移動します。

コール スタックは、タスクがキューに追加された順序で処理されることを意味するコール スタックとは対照的に、タスクが FIFO 順 (先入れ先出し) で処理されることを意味するキュー データ構造のように機能します。

コールバック キュー

イベントループとは?

JavaScript イベント ループは、コール スタックが空になるとすぐに、タスクをコールバック キューからコール スタックに FIFO 順で追加します。

コール スタックが現在何らかのコードを実行している場合、イベント ループはブロックされ、スタックが再び空になるまでキューから追加の呼び出しを追加しません。これは、JavaScript コードが run-to-completion 方式で実行されるためです。

上記の概念を例で理解しましょう。

  • 最初に、Global Execution Context内部でコード用に作成され、コードをcall stack1GEC行ずつ実行します。
  • GEC最初の行を実行し、コンソールに「開始」を出力します。
  • 2 行目を実行すると、setTimeout()Web API が呼び出され、setTimeout()タイマー機能にアクセスできるようになります。5000msその後、遅延時間として設定できるようになります。
  • callBack()を介して関数を渡す場合setTimeout()、 はcallBack()Web Web API 経由のコール バックとして登録されます。
  • 次に、GEC最初の行を実行し、コンソールに「End」を出力します。
  • すべてのコードが実行されるGECと、call stack.
  • その後5000 millisecond、 にcallBack()登録されている関数をweb API内に移動しcall Back Queueます。
  • Event loopcallBack()関数を に入れcall Stackたら、すべての作業が完了します。最後に、callBack()関数が実行され、「Call Back」がコンソールに出力されます。

function f1() {
    console.log('f1');
}

function f2() {
    console.log('f2');
}

function main() {
    console.log('main');
    
    setTimeout(f1, 0);
    
    f2();
}

main();

「f1」が「f2」の前に印刷されると考えている場合は、間違っています。そうなる -

main
f2
f1

説明した JavaScript のメカニズムは、あらゆるコールバック関数または API リクエストに適しています。

ランタイム環境内で 2 番目の例がどのように機能するかを段階的に観察しましょう。

  1. 最初GECは 内に作成され、call stackコードは 内で 1 行ずつ実行されGECます。すべての関数定義をMemory Heapに格納します。
  2. main()呼び出されると、 aFunction Execution Context (FEC)が作成され、コール スタック内に入ります。その後、main()関数のすべてのコードが行ごとに実行されます。
  3. メインという単語を出力するコンソールログがあります。したがって、console.log('main')実行されてスタックから出ます。
  4. setTimeout()ブラウザ API が実行されます。コールバック関数はそれをコールバック キューに入れます。しかし、スタックでは、通常どおり実行が行われるためf2()、スタックに入ります。実行のコンソール ログf2()。どちらもスタックから出ます。
  5. そして、これmain()もスタックから飛び出します。
  6. イベント ループは、コール スタックが空であることを認識し、キューにコールバック関数があります。したがって、コールバック関数f1()は によってスタックに入れられますevent loop。実行が開始されます。コンソール ログが実行f1()され、スタックからポップアウトされます。最後に、スタックとキューには、さらに実行するものは何もありません。

それは私の練習とメモです。役に立った場合は、下の拍手アイコンをクリックしてサポートを示してください。mediumでフォローできます