JavaScript実行メカニズムの詳細な紹介

JavaScript実行メカニズムの詳細な紹介

序文:

仕事でも面接でも、コードの実行順序を知る必要がある場面に遭遇することが多いので、JavaScript の実行メカニズムを徹底的に理解するために時間を費やすつもりです。

JavaScript の実行メカニズムを理解するには、次のことを知っておく必要があります。 (Node 環境とは異なるブラウザ環境を例に挙げます)

1. プロセスとスレッドの概念

  • ブラウザの原則
  • イベント ループ (Event-Loop)、タスク キュー (同期タスク、非同期タスク、マイクロタスク、マクロタスク)
  • プロセスとスレッド

コンピュータの中核はすべてのコンピューティング タスクを実行する CPU であり、オペレーティング システムはコンピュータの管理者であり、タスクのスケジュール、リソースの割り当てと管理を担当し、コンピュータのハードウェア全体を制御します。アプリケーションは特定の機能を備えたプログラムであり、プログラムはオペレーティング システム上で実行されることは誰もが知っています。

プロセス:

プロセスとは、データセット上で独立した機能を持つプログラムの動的な実行プロセスです。これは、オペレーティングシステムによるリソースの割り当てとスケジュールの独立した単位です。これは、アプリケーションプログラムを実行するためのキャリアです。プロセスは、リソースを所有し、独立して実行できる最小単位であり、プログラム実行の最小単位でもあります。

プロセスの特性:

  • 動的性:プロセスはプログラムの実行プロセスです。一時的なものであり、ライフサイクルがあり、動的に生成および破棄されます。
  • 同時実行性:任意のプロセスを他のプロセスと同時に実行できます。
  • 独立性:プロセスは、システム内のリソースの割り当てとスケジュール設定のための独立した単位です。
  • 構造:プロセスは、プログラム、データ、プロセス制御ブロックの 3 つの部分で構成されます。

スレッド:

スレッドは、プログラム実行における単一の連続制御フローであり、プログラム実行フローの最小単位であり、プロセッサのスケジューリングとディスパッチの基本単位です。プロセスには 1 つ以上のスレッドがあり、各スレッドはプログラムのメモリ空間 (つまり、プロセスのメモリ空間) を共有します。標準スレッドは、スレッド ID、現在の命令ポインター (PC)、レジスタ、およびスタックで構成されます。プロセスは、メモリ空間 (コード、データ、プロセス空間、開いているファイル) と 1 つ以上のスレッドで構成されます。

プロセスとスレッドの違い:

  • スレッドはプログラム実行の最小単位であり、プロセスはオペレーティング システムによるリソース割り当ての最小単位です。
  • プロセスは 1 つ以上のスレッドで構成されます。スレッドはプロセス内のコードの異なる実行ルートです。
  • プロセスは互いに独立していますが、同じプロセス内のスレッドはプログラムのメモリ空間 (コード セグメント、データ セット、ヒープなどを含む) と一部のプロセス レベルのリソース (開いているファイルやシグナルなど) を共有します。プロセスは互いには見えません。
  • スケジューリングと切り替え: スレッド コンテキストの切り替えは、プロセス コンテキストの切り替えよりもはるかに高速です。

JS がシングルスレッドなのはなぜですか?

JavaScript 、登場以来、ブラウザのスクリプト言語として使用され、主にユーザー インタラクションの処理と DOM の操作に使用されています。これにより、シングル スレッドのみが可能になり、そうでない場合は非常に複雑な同期の問題が発生します。

たとえば、 JS がマルチスレッドの場合、1 つのスレッドは DOM 要素を変更しようとし、別のスレッドは DOM 要素を削除しようとします。このとき、ブラウザはどちらをリッスンすればよいかわかりません。そのため、複雑さを避けるために、JavaScript は最初からシングルスレッドで設計されました。

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

2. ブラウザの原則

フロントエンドエンジニアとしてはブラウザに精通している必要がありますが、ブラウザはマルチプロセスです。

ブラウザのコンポーネント:

  • ユーザー インターフェイス: アドレス バー、進む/戻る/更新/ブックマークなどが含まれます。
  • ブラウザエンジン: ユーザーインターフェースとレンダリングエンジンの間で指示を転送します
  • レンダリングエンジン: 要求されたコンテンツを描画するために使用される
  • ネットワーク: httpリクエストなどのネットワーク呼び出しを完了するために使用され、プラットフォームに依存しないインターフェースを持ち、さまざまなプラットフォームで動作できます。
  • JavaScript インタープリタ: JavaScriptコードを解析して実行するために使用されます
  • ユーザーインターフェースバックエンド: コンボボックスやウィンドウなどの基本的なウィジェットを描画するために使用され、下部にオペレーティングシステムのユーザーインターフェースを使用します。
  • データ ストレージ: 永続化レイヤーに属します。ブラウザーは、 cookieに似たさまざまなデータをハードディスクに保存します。HTML5 は、軽量で完全なクライアント ストレージ テクノロジである Web データベース テクノロジを定義します。

⚠️注意: ほとんどのブラウザとは異なり、Chrome ではタブごとに個別のレンダリング エンジン インスタンスがあります。各タブは別々のプロセスです

ブラウザはどのようなプロセスで構成されていますか?

ブラウザプロセス:

  • ブラウザのメインプロセス(調整と制御を担当)は1つだけです。
  • ブラウザ インターフェースの表示とユーザーとのやり取りを担当します。前進、後退など。
  • さまざまなページの管理、他のプロセスの作成と破棄を担当します
  • Rendererプロセスによって取得されたメモリ内のBitmapをユーザーインターフェイスに描画します。
  • ネットワークリソース、ダウンロードなどの管理。

サードパーティプラグインのプロセス:

  • サードパーティのプラグインの管理を担当

GPU プロセス:

  • 3D描画とハードウェアアクセラレーションを担当(最大1つ)

レンダリングプロセス:

  • ページドキュメントの解析、実行、レンダリングを担当します

レンダリング プロセスにはどのようなスレッドが含まれていますか?

GUI レンダリング スレッド:

  • 主に HTML、CSS の解析、DOM ツリーの構築、レイアウト、描画などを担当します。
  • このスレッドはJavaScriptエンジン スレッドと相互に排他的です。JavaScript エンジン スレッドが実行されると、GUI レンダリング スレッドは中断されます。タスク キューがアイドル状態のときは、メイン スレッドが GUI レンダリングを実行します。

JavaScript エンジン スレッド:

  • 主にJavaScriptスクリプトの処理とコードの実行(V8エンジンなど)を担当します。
  • ブラウザでは、JS プログラムを同時に実行できる JS エンジン スレッドは 1 つだけです。つまり、JS はシングル スレッドです。
  • JSエンジンスレッドとGUIレンダリングスレッドは相互に排他的であるため、JSエンジンはページレンダリングをブロックします。

タイミングトリガースレッド:

  • タイマー関数( setTimeout,setInterval )の実行を担当します。
  • ブラウザタイミングカウンターはJSエンジンではカウントされません(JSはシングルスレッドなので、ブロック状態になるとカウンターの精度に影響します)
  • タイミングを計り、トリガーするために別のスレッドが使用されます (タイミングが完了すると、イベント トリガー スレッドのイベント キューに追加され、実行前に JS エンジンがアイドル状態になるのを待機します)。このスレッドはタイミング トリガー スレッドであり、タイマー スレッドとも呼ばれます。
  • W3C は HTML 標準で、 setTimeout内の 4 ミリ秒未満の時間間隔は 4 ミリ秒としてカウントされることを規定しています。

イベントトリガースレッド:

  • 準備されたイベントをJSエンジンスレッドに渡して実行する責任があります。
  • イベントがトリガーされると、スレッドは対応するイベントを処理対象のキューの最後に追加し、JS エンジンが処理するのを待機します。

非同期リクエストスレッド:

  • XMLHttpRequest接続後、ブラウザはスレッドを開きます。
  • リクエスト ステータスの変更を検出すると、対応するコールバック関数がある場合、非同期リクエスト スレッドはステータス変更イベントを生成し、対応するコールバック関数をキューに入れて、JS エンジンの実行を待機します。

3. 同期と非同期

JavaScriptはシングルスレッドなので、そのタスクは同期タスクのみにすることはできません。時間のかかるタスクも同期タスクとして実行されると、ページがブロックされます。

したがって、JavaScript タスクは一般的に次の 2 つのカテゴリに分類されます。

同期タスク:

同期タスクとは、メイン スレッドで実行するためにキューに入れられたタスクを指します。次のタスクは、前のタスクが完了した後にのみ実行できます。

非同期タスク:

非同期タスクとは、メインスレッドに入らずに「タスクキュー」(イベントキュー)に入るタスクを指します。「タスクキュー」がメインスレッドに非同期タスクが実行可能であることを通知した場合のみ、タスクはメインスレッドに入って実行されます。

一般的な非同期タスク:タイマー、Ajax、イベント バインディング、コールバック関数、 promiseasync awaitなど。

  • 同期タスクと非同期タスクは、それぞれ異なる実行「場所」に入ります。同期タスクはメイン スレッドに入り、非同期タスクはイベント テーブルに入り、関数を登録します。
  • イベント テーブルで指定された処理が完了すると、この機能はイベント キューに移動されます。
  • メインスレッドのタスクが完了して空になると、 Event Queueに移動して対応する関数を読み取り、メインスレッドで実行します。
  • 上記のプロセスは継続的に繰り返され、イベント ループと呼ばれることもあります。
  • メインスレッド実行スタックが空であることをどうやって知るのか、という疑問が湧いてきます。 js エンジンには、メイン スレッドの実行スタックが空かどうかを継続的にチェックするmonitoring processがあります。空になると、 Event Queueに移動して、呼び出しを待機している関数があるかどうかを確認します。

マクロタスクとマイクロタスク:

広範な同期タスクと非同期タスクに加えて、JavaScript にはよ​​り洗練されたタスク定義もあります。

  • マクロタスク:グローバルコード、setTimeout、setInterval を含む
  • マイクロタスク: new Promise().then(callback) process.nextTick()

タスクの種類によって、タスク キューに入るタスクも異なります。

イベント ループの順序によって、js コードの実行順序が決まります。全体のコード(マクロタスク)を入力すると、最初のループが始まります。次に、すべてのマイクロタスクを実行します。次に、マクロ タスクから再度開始し、完了するタスク キューの 1 つを見つけて、すべてのマイクロ タスクを実行します。

4. 実行スタックとタスクキュー

実行スタック:

JavaScript コードは実行コンテキストで実行されます。JavaScript には 3 つの実行コンテキストがあります。

  • グローバル実行コンテキスト
  • 関数実行コンテキスト、JS関数が呼び出されると関数実行コンテキストが作成されます
  • eval 実行コンテキスト、eval 関数によって生成されるコンテキスト (あまり使用されない)

一般的に、JS コードには複数のコンテキストがありますが、これらのコンテキストの実行順序はどうなっているでしょうか?

スタックは後入れ先出しのデータ構造であることは誰もが知っています。JavaScript の実行スタックはそのようなスタック構造です。JS JavaScriptがコードを実行すると、グローバル コンテキストが生成され、実行スタックにプッシュされます。関数呼び出しが発生するたびに、関数実行コンテキストが生成され、実行スタックにプッシュされます。エンジンはスタックの上から関数の実行を開始し、実行後に実行コンテキストをポップします。

関数add(){
  コンソール.log(1)
  関数foo()
  コンソール.log(3)
}

関数foo(){
  コンソール.log(2)
}
追加()

上記のコードの実行スタックを見てみましょう。

タスクキュー:

先ほど、 JavaScriptのすべてのタスクは同期タスクと非同期タスクに分かれていると述べました。同期タスクは、その名前が示すように、すぐに実行されるタスクです。通常、同期タスクは実行のために直接メイン スレッドに入ります。非同期タスクはタスク キューに入り、メイン スレッド内のタスクが完了するまで待機してから実行されます。

タスク キューは、関連する非同期タスクが実行スタックに入ることができることを示すイベントのキューです。メイン スレッドはタスク キューを読み取り、そこに含まれるイベントを読み取ります。

キューは先入れ先出しのデータ構造です。

前述のように、非同期タスクはマクロタスクとマイクロタスクに分けることができるため、タスク キューもマクロタスク キューマイクロタスク キューに分けることができます。

  • Macrotask Queue : setTimeoutsetInterval 、ユーザー操作、UI レンダリングなどの比較的大きなタスクを実行します。
  • Microtask Queue : より小さなタスクを実行します。一般的に使用されるのはPromiseProcess.nextTickです。

5. イベントループ

  • 同期タスクは実行のためにメインスレッドに直接配置され、非同期タスク (クリック イベント、タイマー、Ajax など) はバックグラウンドで実行され、I/O イベントが完了するか動作イベントがトリガーされるのを待機します。
  • システムはバックグラウンドで非同期タスクを実行します。非同期タスク イベント (または動作イベント) がトリガーされると、タスクはタスク キューに追加され、各タスクは処理のためのコールバック関数に対応します。
  • ここで、非同期タスクはマクロタスクとマイクロタスクに分けられます。マクロタスクはマクロタスク キューに入り、マイクロタスクはマイクロタスク キューに入ります。
  • 実行タスクキュー内のタスクは実行スタック内で完了します。メインスレッド内のタスクがすべて実行されると、マイクロタスクキューが読み込まれます。マイクロタスクがある場合は、すべて実行されてからマクロタスクキューが読み込まれます。
  • 上記のプロセスは継続的に繰り返されます。これはEvent-Loop ) と呼ばれることが多いものです。

検証例:

それを検証するために質問を見てみましょう

(非同期() => {
    コンソール.log(1) 
  
    タイムアウトを設定する(() => {
    コンソールログ('setTimeout1')
    }, 0);
  
    関数foo(){
        新しい Promise((res,rej) => { を返す
            コンソール.log(2)
            解像度(3)
        })
    }
  
    新しいPromise((resolve,reject)=>{
    コンソール.log(4)
    解決する() 
    コンソール.log(5)
    }).then(()=> {
    コンソールログ('6')
    })
  
    const res = foo() を待機します。
    コンソールログ(res);
    コンソールログ('7')
  
    setTimeout(_ => console.log('setTimeout2'))
})()

印刷順序は、1、4、5、2、6、3、7、setTimeout1、setTimeout2 です。

分析:

  • コードは上から下へ実行されます。まず、 console.log(1)に遭遇し、直接 1 を出力します。次に、マクロ タスクに属するタイマーに遭遇し、それをマクロ タスク キューに入れます。
  • 再び promise に遭遇すると、 new Promiseは同期タスクなので、4 を直接出力します。後続のthen関数である resolve に遭遇すると、それをマイクロタスク キューに入れて 5 を出力します。
  • 次に、await foo を実行します。foo 関数にはpromiseがあります。 new promise同期タスクに属しているため、2 が直接出力されます。await が返すのはpromiseコールバックです。await の後のタスクは、マイクロタスク キューに入れられます。
  • 最後に、タイマーが検出され、マクロタスクキューに入れられます。
  • 実行スタックタスクが実行された後、マイクロタスクキューに移動してマイクロタスクの実行を取得します。最初に最初のマイクロタスクを実行し、6を出力し、次に2番目のマイクロタスクを実行し、3、7を出力します。
  • マイクロタスクが実行された後、マクロタスクキューに移動してマクロタスクの実行を取得し、 setTimeout1setTimeout2を出力します。

6. タイマー

JavaScriptのタスク キュー内の非同期タスクには、特定のコードが実行されるまでの時間を指定するタイマー イベントも含まれます。タイマー機能は、主にsetTimeout()nterval() 2 つの関数によって実行されます。これらの内部実行メカニズムはまったく同じです。主な違いは、 setTimeoutは 1 回限りの実行プロセスであるのに対し、 setInterval繰り返し実行されるプロセスである点です。

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

遅延時間を 0 に設定すると、すぐに実行されますか?

タイムアウトを設定します(()=>{
    コンソール.log(1)
},0)

コンソール.log(2)

しかし、そうではありません。上記の印刷結果では、最初に 2 が印刷され、次に 1 が印刷されます。混乱していると感じますか?

上記のイベント ループのルールを使用すると、非常にわかりやすくなります。グローバル コードが実行され、タイマーsetTimeoutに遭遇すると、マクロ タスク キューに入れられ、次に同期コードが下方向に実行され、2 が印刷されます。スタック タスクが実行された後、マイクロ タスク キューに移動します。マイクロ タスクがない場合は、マクロ タスク キューをもう一度確認します。マクロ タスクがあり、print 1 が実行されます。

setTimeout(fn,0) の意味は、タスクがメインスレッドの最も早い利用可能なアイドル時間に実行されること、つまりできるだけ早く実行されることを指定することです。 「タスク キュー」の最後にイベントを追加するため、同期タスクと「タスク キュー」内の既存のイベントが処理されるまで実行されません。

HTML5 標準では、setTimeout() の 2 番目のパラメータの最小値 (最短間隔) は 4 ミリ秒未満であってはならないと規定されています。この値より小さい場合は、自動的に増加します。以前のバージョンのブラウザでは、最小間隔は 10 ミリ秒に設定されていました。さらに、DOM の変更 (特にページの再レンダリングを伴うもの) は通常、すぐに実行されるのではなく、16 ミリ秒ごとに実行されます。現時点では、 requestAnimationFrame()方が setTimeout() よりも優れています。

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

JavaScript 実行メカニズムの詳細な紹介に関するこの記事はこれで終わりです。JavaScript 実行メカニズムに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScriptイベント実行メカニズムの深い理解
  • いくつかの面接の質問を使ってJavaScriptの実行メカニズムを調べる
  • JavaScript実行メカニズムの詳細な説明
  • JavaScriptの実行メカニズムを徹底的に理解する

<<:  CSS で text-align と margin: 0 auto を使用して中央に配置する例コード

>>:  Nginx がサーバーの生存状態をパッシブにチェックする詳細な説明

推薦する

MySQL の時間設定に関する考慮事項の詳細な要約

時間は本当に存在するのでしょうか?時間は人間が考え出した概念に過ぎず、物事の変化を測る基準に過ぎない...

2012年のベストWebデザイン作品レビュー[パート1]

新年の初めに、友人の健康と2013年が素晴らしい年となることを心からお祈りいたします。この記事では、...

Windows 10 でカスタムドメイン名をバインドするように Hexo と GitHub を構成する方法

Hexo は Windows 10 でカスタムドメイン名を GitHub にバインドしますまずドメイ...

ユーザー エクスペリエンス デザイナーとは誰ですか?

怖いですね! 写真の翻訳: (内側から外側へ)最初のレイヤー:ユーザーエクスペリエンス第2層:コンテ...

MySQLデータベースを別のマシンに移行する方法の詳細な説明

1. まず、移行サーバー上のデータ ファイルを見つけます。MySQL 5.7 とデフォルトのインスト...

MySQLデータベースの増分バックアップのアイデアと方法

MySQL データベースの増分バックアップを実行するには、データベース構成ファイル /etc/my....

MySQL 8.0.26 のインストールと簡易チュートリアル (インターネット上で最も完全)

目次1. MySQLをダウンロードする1.1 ダウンロード1.2 インストール1. MySQLをダウ...

MySQL 文字列分割操作 (区切り文字を含む文字列のインターセプション)

区切り文字なしの文字列抽出質問の要件データベース内のフィールド値:実装効果: 1行のデータを複数行に...

まだ*を選択しますか?

アプリケーションが牛のように遅い理由は数多くあります。ネットワーク、システム アーキテクチャ、または...

ReactのuseEffectクロージャの落とし穴についての簡単な説明

問題コードuseEffectによって発生したクロージャの問題コードを見てみましょう 定数 btn =...

Vue がコンポーネント通信を実装する 8 つの例

目次1. Props 親コンポーネント ---> 子コンポーネント通信2. $emit 子コン...

WeChatミニプログラム抽選コンポーネントの使い方

WeChatコンポーネントの形式で提供されます。コンポーネント内部ではasync/awaitが使用さ...

主軸上のFlex子要素の比率を制御する方法

背景フレックス レイアウトにより、配置とスペースの割り当てがより効果的に実現されます。最近、flex...

Webフロントエンドの一般的な操作(JS/HTML/CSSなどの知識を含む)

ul liの前のアイコン1をキャンセルしますクリア値1値を1に設定ラベル中央値1をクリアラベルの中央...

簡潔なReactコンポーネントを書くためのヒント

目次スプレッド演算子を使用してプロパティを渡すのは避けてください関数パラメータをオブジェクトにカプセ...