JavaScriptエンジンでの処理を理解する
どうもフロントエンドエンジニアのoreoです。
前回はWebブラウザでのRenderingエンジンの仕組みについて整理しました(👇)。
今回の記事では、Renderingエンジンと同じく、Webブラウザに搭載されているJavaScriptエンジンでの処理についてまとめたいと思います。ASTなどを知っていると、普段使っているESLint等のツールの動作理解に役立ちそうですね。
1 JavaScriptエンジンとは?
JavaScriptエンジンは、JavaScriptコードを実行するプログラムです。Webブラウザは、JavaScriptエンジンを搭載しており、Renderingエンジンから引き渡されたJavaScriptのコードを実行します。また、JavaScriptエンジンは、Node.js、DenoなどのサーバーサイドのJavaScript実行環境にも搭載されています。
ちなみに、JavaScriptエンジンとそれを使用しているWebブラウザの関係は下記になります(※少し古い部分があるかも知れません、、)。
JavaScript engine | Browser |
---|---|
V8 | Google Chrome、Opera |
Chakra | Microsoft Edge |
JavaScriptCore(Nitro) | Safari |
SpiderMonkey | Firefox |
参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/About_JavaScript
https://gihyo.jp/book/2017/978-4-7741-8967-3
2 JavaScriptエンジンでの処理の流れ
JavaScriptが実行されるまでの過程は、JavaScriptエンジンによって異なりますが、一般的には下記のようになります。
大きな流れとしてJavaScriptコードから、Token、AST(Abstract Syntax Tree、抽象構文木)を経て、CPUが実行可能なByte Codeにコンパイルされ、JavaScriptは実行されます。
例えは、console.log("Hello World !");
というコードがByte Codeに変換されるまでのイメージ図は下記のようになります。
それでは、この図を例にして、①字句解析、②構文解析、③コンパイルの各処理の概要を見ていきます。
2-1 ①字句解析(JavaScriptコード→Tokenに変換)
ここではJavaScriptのソースコードを、Tokenと呼ばれる塊に分解します。それぞれのTokenにはIdentifier(識別子)、Operator(演算子)、String(文字列)、Parenthesis(括弧)などのように意味を持ったラベルが付けられます。
2-2 ②構文解析(Token→ASTに変換)
次に、TokenをASTに変換します。JavaScriptにおけるASTは、ESTreeに則って、さまざまなプロパティを持ったノードと呼ばれる単位が組み合わさったツリー状のデータ構造を取ります。
ASTという構造をとることで、プログラムがJavaScriptのコード変更や検証を行うことができます。例えば、ESLintでは、ASTを経由してJavaScriptコードの検証を行っています。
JavaScriptのコードが、どのようなASTに変換されているのかは、下記サイトで確認可能です。興味がある方は確認ください。
例で用いたconsole.log("Hello World !");
というコードは、下のようなASTに変換されます。
{
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"computed": false,
"object": {
"type": "Identifier",
"name": "console"
},
"property": {
"type": "Identifier",
"name": "log"
}
},
"arguments": [
{
"type": "Literal",
"value": "Hello World !",
"raw": "\"Hello World !\""
}
]
}
}
],
"sourceType": "script"
}
なお、JavaScriptコードがASTに変換されるまでをParse(解析)と呼びます。
2-3 ③コンパイル(AST→ByteCodeに変換)
続いて、ASTをByte Codeに変換します。コンパイル処理は、JavaScriptエンジンによって異なります。代表的な方式として、JITコンパイラ(Just-In-Time Compiler)があり、V8やJavaScriptCore(Nitro)などで採用されています。
通常のコンパイラでは、事前にソースコード(または中間コード)からbytecodeヘの変換を行いますが、JITコンパイルではプログラム実行時にbytecodeヘの変換を行います。単純なインタプリター(1行ずつ実行していく方式)と比べて、コンパイル時のオーバーヘッド(システムへの負荷)が大きいなどのデメリットもある一方で、実行時の速度が速いというメリットがあります。
ちなみに、V8では、JITコンパイル時に、型情報などから既に変換したbytecodeを再利用するなどの最適化処理がなされて、実行時間の高速化が図られています。
3 最後に
今回の記事で、JavaScriptエンジンでの、大まかな処理の流れを知ることができました。いずれの処理もさらに深掘りしがいがあるtopicなので、時間を見つけて深掘りたいと思います!
4 参考
Discussion