🚂

JavaScriptエンジンでの処理を理解する

2022/07/11に公開

どうもフロントエンドエンジニアのoreoです。

前回はWebブラウザでのRenderingエンジンの仕組みについて整理しました(👇)。

https://zenn.dev/oreo2990/articles/280d39a45c203e

今回の記事では、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に変換されているのかは、下記サイトで確認可能です。興味がある方は確認ください。

https://esprima.org/demo/parse.html?code=%2F%2F Life%2C Universe%2C and Everything console.log("Hello World !")%3B

例で用いた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 参考

https://gihyo.jp/book/2017/978-4-7741-8967-3

https://dev.to/lydiahallie/javascript-visualized-the-javascript-engine-4cdf

https://zenn.dev/kazuwombat/articles/2a870356528783#ast(抽象構文木)について

https://zenn.dev/antez/books/568dd4d86562a1/viewer/8de90b

https://zenn.dev/canalun/articles/exec_javascript_beyond_ast#3.-getting-feedback%2C-optimize-%26-compile-it(さて、これはいったい……?)

Discussion