JS の仕組み
V8 エンジンの原理について詳しくはこちら
ブラウザ カーネルは、Webkit を例として、2 つの部分で構成されています。
- WebCore: HTML の解析、レイアウト、レンダリング、およびその他の関連作業を担当します。
- JavaScriptCore: JavaScript コードを解析して実行します。
V8 エンジンの公式定義:
- V8 は、C++ で書かれた Google のオープンソースの高性能 JavaScript および WebAssembly エンジンで、特に Chrome や Node.js で使用されています。
- ECMAScript と WebAssembly を実装しており、
x64、IA-32、ARM、または MIPS プロセッサを使用する Windows 7 以降、macOS 10.12 以降、および Linux システム上で実行されます。 - V8 はスタンドアロンで実行することも、任意の C++ アプリケーションに埋め込んで実行することもできます。
V8 エンジンのアーキテクチャは非常に複雑なので、まずその巨大なエンジンのいくつかのモジュールを理解することができます。
- インタプリタは JavaScript コードを直接理解しないため、Parse モジュールは JavaScript コードを AST (抽象構文ツリー) に変換します。
- 関数が呼び出されない場合、AST に変換されません。
- Parse の V8 公式ドキュメント: https://v8.dev/blog/scanner
- IgnitionはASTをByteCode(バイトコード)に変換するインタープリタです
- 同時に、TurboFanの最適化に必要な情報(関数パラメータの型情報など、その型のみで実際の操作が実行できる)が収集されます。
- 関数が 1 回だけ呼び出された場合、Ignition は ByteCode を解釈して実行します。
- Ignition の V8 公式ドキュメント: https://v8.dev/blog/ignition-interpreter
- TurboFan は、バイトコードを CPU が直接実行できるマシンコードにコンパイルするコンパイラーです。
- 関数が複数回呼び出された場合、その関数はホット関数としてマークされ、コードの実行パフォーマンスを向上させるために TurboFan によって最適化されたマシンコードに変換されます。
- マシン コードは実際には ByteCode に復元されます。これは、その後の関数の実行中に型が変更された場合 (たとえば、sum 関数は最初は数値型で
実行されマシンコードは操作を正しく処理できないため、バイトコードに逆変換されます。 - TurboFan の V8 公式ドキュメント: https://v8.dev/blog/turbofan-jit
V8 アーキテクチャ分析図は公式から引用
コードを解析する手順:
- コードを取得した後、V8 はストリーム入力を使用して字句解析を渡し、それをトークンに分析します。
- 解析/事前解析して実行ノードを 1 つずつ生成します
- ASTツリーの生成
- バイトコードに変換するためのホットスポット方法がある場合は、ターボファン コンパイラーを使用してそれを機械コードに最適化し、パフォーマンスを向上させます。
グローバルなコード実行プロセス
JS エンジンは、コードを実行する前にヒープ メモリにグローバル オブジェクトを作成します: Global Object (GO)
- オブジェクトはすべてのスコープからアクセス可能です
- これには、日付、配列、文字列、数値、setTimeout、setInterval などが含まれます。
- それ自体を指す window 属性もあります
js エンジン内には実行コンテキスト スタック (Execution Context Stack、略して ECS) があり、これはコードを実行するために使用される呼び出しスタックです。
彼が実行するグローバル コード ブロックの機能は次のとおりです。
- コードを実行するためにグローバル実行コンテキスト GEC グローバル コンテキストを構築する
- この構築されたコンテキストを実行スタックに追加するということは、GEC を ECS に入れることを意味します。
GEC は ECS に配置され、次の 2 つの部分で構成されます。
- コードが実行される前、パーサーを AST に変換するプロセス中に、グローバルに定義された変数、関数などが GlobalObject に追加されますが、値は割り当てられません。
- コードの実行中に、変数に値を代入するか、他の関数を実行します。
各実行コンテキストは VO (Variable Object、変数オブジェクト) に関連付けられ、変数や関数の宣言はこの VO オブジェクトに追加され、グローバル コードが実行されると VO が GO オブジェクトになります。
グローバルコンテキストを理解するための 3 つの鍵:
- VO(ゴー)
- スコープチェーン
- これ
以下のコード処理を実行します
var message = "Global Message"
function foo() {
var message = "Foo Message"
}
var num1 = 10
var num2 = 20
var res = num1 + num2
console.log(res);
グローバルコード実行前
コードを実行した後
関数コード実行処理
実行中に関数が実行されると、関数本体に基づいて関数実行コンテキスト (略して FEC) が作成され、EC スタックにプッシュされます。
- 関数実行コンテキストに入ると、AO オブジェクト (Activation Object) が作成されます。
- この AO オブジェクトは引数を使用して初期化され、初期値は渡されたパラメーターです。
- この AO オブジェクトは、変数の初期化を保存する実行コンテキストの VO として使用されます。
以下の関数実行処理
実行前
処刑後
プロセスは次のとおりです。
- 関数実行コンテキストである実行前に FEC を作成します。
- 関数名を名前とする AO オブジェクトを作成します。
- スコープチェーンの作成
- コードを保存する関数オブジェクトを生成する
- シビング (まだ何もありません)
- 次にコードを上から下に実行します
- 実行が完了すると、名前はundefineに変更されます。
スコープとスコープチェーン
実行コンテキストを入力すると、実行コンテキストはスコープ チェーン (スコープ チェーン) にも関連付けられます。
- スコープ チェーンは、変数識別子の評価に使用されるオブジェクトのリストです。
- 実行コンテキストに入ると、このスコープ チェーンが作成され、コード タイプに基づいて一連のオブジェクトが追加されます。
追伸: スコープが改善されます vo自体が無い場合は上位層に行って検索します 先に出力してから宣言するとunknownが出力されます これも確認されました。
スコープ改善演習
var n = 100
function foo(){
n=200
}
foo()
console.log(n)
N =200
順次メモリ検索の図は次のとおりです。
- グローバル コード作成関数は n を見つけて関数 vo に入れ、その後 foo() を呼び出します。
- 関数呼び出し後の GO で n 個のコピーを検索する
- 関数は終了し、n を出力します。
スコープ チェーンも JS クロージャの焦点です。JS のクロージャはスコープ チェーンを使用して、スコープ間で変数にアクセスできるようにします。これにより、開発効率が向上し、多くのトラブルが軽減されます。