Javascript の内部構造の詳細

Feb 02 2023
Javascript エンジン、イベント ループ、Web API、イベント キュー、コールバックの内部動作。はじめに Javascript がなかったらどうなるか想像したことがありますか? Javascript がなければ、Netflix で映画をストリーミングしたり、Amazon で買い物をしたり、Youtube でビデオを視聴したりすることはできません。

Javascript エンジン、イベント ループ、Web API、イベント キュー、コールバックの内部動作。

JavaScript

序章

Javascript がなかったらどうなるか想像したことがありますか? Javascript がなければ、Netflix で映画をストリーミングしたり、Amazon で買い物をしたり、Youtube でビデオを視聴したりすることはできません。他のほとんどの Web サイトは Javascript を使用しており、Web ページをインタラクティブにしています。

Javascript が発明されるまで、インターネットは単なるHTMLページで構成されていました。HTMLとCSS は、 Web ページに構造とスタイルを与えます。しかし、JavaScript は Web ページを動的にします。資格情報を入力して Web サイトにログインしたり、オンライン取引を実行したり、ゲームをプレイしたりできます。Javascript はあらゆることを実現します。

Stack-overflow で実施された調査の 1 つでは、GitHub 上で最も多くのリポジトリが Javascript にあることが示されました。毎年、JavaScript で新しいフレームワークとライブラリが開発されています。この言語はインターネットを真に変革し、人々の生活を簡素化しました。

バックグラウンド

2 年前、私はサイドプロジェクトに取り組んでいて、初めて Javascript を使用しました。私は過去 10 年来の経験豊かなプログラマーなので、構文に精通し、3 か月で優れたコマンドを開発しました。

何か月も経つにつれて、私が開発したウェブページのパフォーマンスが低下してきました。読み込みに時間がかかり、顧客に影響を及ぼし始めました。そこで、根本原因を特定するために徹底的に調査しました。

眠れない夜を過ごした私は、ついに限界に達し、諦めようとしていた。しかし、最後に試してみることにしました。最後に、JavaScript が内部でどのように動作するかを徹底的に理解しようと考えました。多くのリソースを参照し、最終的に 2 日以内にコードのバグを修正することができました。

エンジニアは通常、新しいものを構築することに集中します。私たちが使用するツールとフレームワークは、複雑さのほとんどを私たちから隠しています。旅をしていると、私たちは物事が舞台裏で実際にどのように機能しているかを忘れがちです。私たちが使用するツールの内部動作を知る必要があります。内部構造をよく理解することは、問題を絞り込み、実用的な解決策を考え出すのに役立ちます。

この記事では、ブラウザで Javascript が実行されるときにバックグラウンドで何が起こっているのかを見ていきます。JavaScript エンジンを見て、その仕組みを理解します。JavaScript ランタイムに関係するさまざまなコンポーネントを確認し、イベント ループとイベント キューを明確にします。これらの用語は馴染みのない用語かもしれませんが、この記事ではそれぞれの用語を簡略化するように努めます。まずは Javascript の内部をマスターすることから始めましょう。

JavaScript

JavaScript は高レベルのインタープリター言語です。C や C++ とは異なり、JavaScript コードをコンパイルして実行する必要はありません。Javascript コードは、まずバイトコードにコンパイルされてから、マシン レベルのコードに変換されます。

Java、C、C++、C# などの高級言語はマルチスレッドをサポートしています。これらの言語を使用すると、プログラマは複数のスレッドを作成し、異なるスレッドでタスクを実行できます。したがって、複数の CPU でタスクを生成し、コンピューティング リソースを効率的に利用できます。

対照的に、JavaScript はシングルスレッドです。これは、常に単一の実行スレッドのみが存在することを意味します。時間がかかる JavaScript 関数を実行すると、Web ページがフリーズします。閲覧中に「Web サイトが応答しません」というエラーが何度も表示されたことがあるかもしれません。

ページが応答しないエラー

簡単なデモを実行して、実際に動作する様子を見てみましょう。Twitter を開いて、Trending、News、Sportsなどの個別のタブをクリックできます。

Twitter のセクションを探索する

現在、コンソールで無限developerループを含む関数を作成しています。以下に関数の定義を記載します。

function infiniteLoop() {
    for(i=0; i < 100000; ) {
    }
}
infiniteLoop()

Sports以下の GIF は、タブをまたはに変更できないことを示していますEntertaintment。

無限ループにより画面がフリーズする

関数には無限ループが含まれているため、無限に実行され続けます。別のタブを選択すると、ブラウザーはclickイベントを処理し、それに応じてクリックされたタブの内容を表示することになっています。Javascript がすでに関数を実行しているため、他のタブを選択できませんinfiniteLoop。

上の例に見られるように、JavaScript では一度に 1 つの関数のみを実行できます。さて、ブラウザはどのようにして複雑な Web サイトを数秒でロードできるのか疑問に思われるかもしれません。

後続のセクションでは、JavaScript がこの魔法を舞台裏でどのように実行するかを理解します。ここで、JavaScript のコア、つまり Javascript エンジンを理解しましょう。

JavaScript エンジン

すべてのブラウザは独自の Javascript エンジンを提供します。例:- Chrome は V8 エンジンを使用しますが、Firefox は SpiderMonkey エンジンを使用します。エンジンは、JavaScript コードを 1 行ずつ実行します。以下は、一般的なブラウザとその JavaScript エンジンのリストです。

Javascript エンジンは、ヒープとスタックで構成されます。これら 2 つの要素の働きを理解しましょう。

ヒープ

ヒープは、関数定義やオブジェクト変数を保存する役割を果たします。これには、コードによって参照されるすべてのオブジェクトが含まれます。

オブジェクトはヒープ内に動的に作成されます。動作はJavaと似ています。オブジェクトが関数によって参照されるたびに、オブジェクトはヒープからフェッチされます。

function bar() {
    var player = { "firstName": "Shubhman",
                   "lastName": "Gill"
                 }
    var teams = ["India", "NewZealand"]
    i =0
    a = 10
    console.log("In the function bar")
}

bar()

      
                
Objects on Heap

スタック

スタックは後入れ先出し (LIFO) データ構造です。関数が実行されるたびに、関数のローカル変数がスタックにプッシュされます。関数の実行が完了すると、スタックから削除されます。実行中に関数が新しい関数を呼び出すと、その新しい関数もスタックにプッシュされます。エンジンはスタックの最上位にある関数を実行します。

スタックの一番下にある関数は、その上のすべての関数が戻ると実行を再開します。関数がエラーをスローした場合、スタック トレースが に出力されますconsole。スタック トレースは、例外がスローされたときに呼び出された関数のチェーンで構成されます。例を見てみましょう。

上記のコードをブラウザで実行してみます。そして関数にブレークポイントを追加しますcity。

ブラウザでの Javascript の実行

上で見たように、スクリプトを実行すると、関数内のブレークポイントで停止しますcity。右側のセクションの上部に関数Call Stackが表示されます。city

はCall Stack、これまでに呼び出されたすべての関数で構成されます。そして、最新の機能が一番上に配置されます。関数の実行が完了すると、スタックから削除されます。

以下の画像では、関数cityが削除され、行番号 9 の関数の実行が継続されていることがわかりますstreet。

上で見たように、JavaScript は一度に 1 つの関数のみを実行でき、実行スレッドは 1 つだけです。実行スタック内で複数の関数を同時に実行することはできません。

呼び出しを実行して別の Web サイトからデータを取得する関数があるとしますHTTP。サーバーが遅く、関数の実行に時間がかかる場合はどうなりますか? 他の機能は実行できなくなり、ユーザー エクスペリエンスは非常に低下します。

これは現実の世界で起こりますか?いいえ、Facebook.com、Twitter.com、Amazon.comなどの複雑な Web サイトでは、ユーザー エクスペリエンスに影響を与えることなく大量の JavaScript コードを実行します。その方法を理解するために、JavaScript ランタイムについて詳しく見ていきます。

JavaScript ランタイム

Javascript は、サーバーからデータを取得したり、DOM を操作したり、タイマーを使用して関数を実行したりするためのさまざまな API を提供します。Javascript はシングル スレッドであるため、Web サイトのレンダリングが停止し、パフォーマンスの低下につながる可能性があります。ただし、JavaScript ランタイムは、このようなことが起こらないようにします。これを例で理解してみましょう。

以下のコードの出力がどのようになるかは推測できます :-

getSubTitles印刷後に印刷されますかgetActors? を呼び出した後setTimeout、コードは停止しますか?

以下は、上記のコードを実行して生成された出力です。

コード出力

上で見られるように、getSubTitles最初に が印刷され、4 秒後に がgetActors印刷されました。ただし、関数はsetTimeout4 秒待機しましたが、それでもgetSubTitles前に後続の関数が実行され、コードは待機しませんでした。舞台裏で何が起こったのかを理解しましょう。

ウェブAPI

setTimeout、 などの関数の実装は、fetchJavaScript ソース コードのどこにも見つかりません。これらの関数はブラウザによって提供されており、JavaScript の一部ではありません。実装はブラウザごとに異なります。

ブラウザは、ネットワーク呼び出しの実行、DOM の操作、オーディオやビデオの処理などを行う機能を提供します。これらの機能は Web API を形成します。内部実装は複雑で、主に C++ コードで構成されています。API は、JavaScript コードから呼び出すことができるインターフェイスを提供します。

などの関数を呼び出すとsetTimeout、JavaScript はこれが Web API であることを認識します。次に、この関数の実行を Web API コンテナに委任します。関数はsetTimeout別のスレッドで実行されます。Javascript の実行スレッドでは実行されません。setTimeoutこれが、その後に記述されたコードが、コールバック関数が渡される前に実行される理由です。

同様に、ネットワーク呼び出しには時間がかかるため、 などの関数はfetch別のスレッドで実行されます。ブラウザー Web API は、複雑さのほとんどを私たちから隠します。例: - の内部実装には、fetchTCP 接続の形成、データの送信、および解析が含まれます。さらに、適切なスレッドで実行をスケジュールすることで、コンピューティング リソースを効率的に使用します。

以下の gif は、 のような Web API 関数を実行すると何が起こるかを示していますsetTimeout。

JavaScript の動作

上で見たように、setTimeout関数が呼び出されると、コールバック関数がgetActorsWeb API に送信されます。Web API コンテナでは、関数はsetTimeout4 秒間待機します。その間、後続の関数getSubTitlesが呼び出されます。

getActorsここで、タイマーが終了した後にコールバック関数がどのように実行されるのか疑問に思われるかもしれません。次のセクションでは、Web API が戻った後にコールバックがどのように実行されるかを見ていきます。

イベントキュー

JavaScript はコールバック関数に大きく依存しています。イベントが発生すると、コールバック関数が実行されます。例:- サーバーから応答を受信した後、そのjson表現を印刷する必要があります。または、4 秒の遅延後に関数を実行します。

Javascript ランタイムは、イベント キューまたはコールバック キューと呼ばれるコンポーネントで構成されます。これはメッセージのキューであり、先入れ先出し (FIFO) 順序を使用します。メッセージは、実行がスケジュールされたコールバック関数にすぎません。

これを理解するために例を挙げてみましょう。以下のコードでは、ボタンのクリック イベントが発生するたびにコールバック関数を登録しています。ユーザーがボタンをクリックすると、関数onClickが呼び出されます。timerこの関数は2 秒の遅延後に関数を呼び出します。

上のアニメーションは、舞台裏で何が起こっているかを示しています。$.onここには 2 つの Web API (a)と b)がありますsetTimeout。最初の API は、クリック イベントが発生するのを待ちます。クリック イベントを受信すると、関数が実行されますonClick。2 番目の API は、関数setTimeOutによって引き続き呼び出されますonClick。

ユーザーがボタンをクリックするたびに、関数がonClickイベント キューにプッシュされます。同様に、タイマーが終了すると、関数はtimerキューにプッシュされます。したがって、イベント キューは、実行する必要があるすべてのコールバック関数で構成されます。この場合、onClick関数ですtimer。

イベントループ

のコールバック関数は、EventQueueイベント ループを介して Javascript の実行スタックに到達します。イベント ループは、現在の Javascript スタックが空かどうかを継続的にチェックします。スタックが空であることが判明した場合、イベント キューから最初のメッセージを選択し、スタックにプッシュします。

前のセクションの例で言えば、イベント ループはスタックが空であることを検出するとすぐに、コールバック キューから次のメッセージを取得して実行します。

以下はイベントループの動作のための大まかな疑似コードです:-

イベント ループは、実行スタックを継続的に監視する無限ループと考えることができます。スタックが空の場合は、イベント キューからメッセージ (関数) を取得して実行します。

以下の図は、JavaScript ランタイムの高レベル表現を示しています。

JavaScript ランタイム

JavaScript ランタイムのさまざまなコンポーネントについて見てきました。これで、JavaScript が内部でどのように動作するかについてよりよく理解できるようになります。これまでに学んだことをまとめてみましょう。

まとめ

  • JavaScript はシングルスレッドであり、一度に 1 つの機能を実行します。
  • Web API を利用して複雑なタスクをバックグラウンドで実行します。
  • Web API の実行が完了すると、コールバック関数がイベント キューに送信されます。
  • イベント ループは、JavaScript スタックが空かどうかを継続的にチェックします。スタックが空の場合は、イベント キューからコールバック関数を選択して実行します。
  • JavaScript の内部とランタイム
  • ブラウザとノードでの Javascript の仕組み
  • Node.js イベント ループのアニメーション ガイド
  • そもそもイベントループとは一体何なのでしょうか?
  • 非同期 JavaScript とイベント ループをゼロから作成する