JavaScriptエンジンの仕組みをGIFアニメで分かりやすく解説
Post on:2020年1月28日
Node.jsおよびChromiumベースのブラウザで使用されるJavaScriptエンジンの仕組みについてGIFアニメーションで分かりやすく解説された記事を紹介します。
🚀⚙️ JavaScript Visualized: the JavaScript Engine
by Lydia Hallie
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
JavaScriptを視覚的に解説: JavaScriptエンジン
JavaScriptはクールです(私はそう思います)が、あなたが書いたコードをマシンが実際に理解するにはどうすればいいのでしょうか。JavaScriptデベロッパーは通常、コンパイラを自分で扱う必要はありません。しかし、JavaScriptエンジンの仕組みを理解し、人に優しいJavaScriptコードをどのように処理するのか、それをマシンが理解できるものに変換する仕組みを確認することは、間違いなくよいことです!🥳
私は視覚学習者なので、GIFアニメで視覚的に説明することで、あなたの手助けをしようと思います。
注: この記事では主に、Node.jsおよびChromiumベースのブラウザで使用されるV8エンジンに基づいています。
HTMLパーサーはソースを含むscriptタグを検出します。このソースのコードはネットワーク、キャッシュ、またはインストールされているService Workerのいずれかからロードされます。レスポンスはバイトストリームとして要求されたスクリプトで、バイトストリームデコーダーが処理します! バイトストリームデコーダーはダウンロード中のバイトストリームをデコードします。
スクリプトは、ネットワーク、キャッシュ、Service WorkerのいずれかからUTF-16バイトストリームとしてロードされ、バイトストリームデコーダーに渡されます。
バイトストリームデコーダーは、デコードされたバイトストリームからトークンを作成します。
上記GIFアニメのバイトストリームデコーダーに渡された文字列を見てましょう。0066はfに、0075はuに、006eはnに、0063はcに、0074はtに、0069はiに、006fはoに、006eはnにデコードされ、その後にスペースがあります。渡されたのは「function」でした! これはJavaScriptの予約キーワードで、トークンが作成されてパーサーに送信されます(プリパーサーは後ほど説明します)。バイトストリームの残りの部分についても同様です。
バイトストリームデコーダーは、バイトをトークンにデコードします。 トークンはパーサーに送信されます。
エンジンは、プリパーサーとパーサーの2つのパーサーを使用します。
プリパーサーは構文エラーがあるかどうか確認するために、トークンを早期にチェックするだけです❌ これにより、後でパーサーによって発見される可能性があるコード内のエラーを見つけるために必要な量を減らすことができます!
エラーがなければ、パーサーはバイトストリームデコーダーから受け取ったトークンに基づいてノードを作成します。そして、これらのノードで抽象構文木(AST)を作成します🌳
パーサーはトークンに基づいてノードを生成し、抽象構文木を作成します。
次は、インタプリタの時間です! ASTを走査し、ASTに含まれる情報に基づいてバイトコードを生成するのがインタプリタです。バイトコードが完全に生成されると、ASTは削除され、メモリスペースがクリアされます。最後に、マシンで使用できるものがあります!🎉
インタプリタはASTを調べてバイトコードを生成します。
バイトコードは高速ですが、より高速になる場合があります。このバイトコードが実行されると、情報が生成されます。特定の動作が頻繁に発生するかどうか、使用されたデータのタイプを検出できます。おそらくあなたは関数を何十回も呼び出しているかもしれませんが、この関数をさらに高速に実行できるように最適化する必要があります🏃🏽♀️
バイトコードは生成されたタイプフィードバックとともに、Optimizing compilerに送られます。Optimizing compilerはバイトコードとタイプフィードバックを受け取り、これらから高度に最適化されたマシンコードを生成します🚀
バイトコードとタイプフィードバックはOptimizing compilerに送られ、高度に最適化されたマシンコードが生成されます。
JavaScriptはデータの型が常に変化できる、動的型付け言語です。もし、JavaScriptエンジンが特定の値のデータ型を毎回チェックしなければならないとしたら、非常に遅くなります。
その代わりに、エンジンはインラインキャッシングと呼ばれる手法を使用します。これは将来同じ振る舞いで同じ値を返すことを期待して、コードをメモリにキャッシュします! ある関数が100回呼び出され、常に同じ値を返したきたとします。そして、101回目にも同じ値が返されることを前提としています。
例えば、下記のsum関数があるとします。この関数は(今のところ)常に数値を引数として呼び出されていました。
常に数値を引数として呼び出されていました。
これは数値の3を返します!
次に呼び出すときも、2つの数値で再度呼び出すと想定されます。
もしこれが正しいなら、動的なルックアップは必要なく、すでに参照されている特定のメモリスロットに保存されている結果を使用できます。また、もし仮定が間違っていたなら、コードの最適化が解除され、最適化されたマシンコードの代わりに元のバイトコードに戻ります。
例えば、次にこの関数を呼び出すときには数値ではなく、文字列にしてみます。JavaScriptは動的に型指定されるため、エラーなしでできます!
数値ではなく、文字列にしてみる
つまり、数値2は文字列に強制変換され、関数は文字列「12」を代わりに返すことを意味します。インタプリタされたバイトコードの実行に戻り、タイプフィードバックをアップデートします。
この記事があなたのお役に立てば幸いです😊
もちろん、この記事で取り上げていないエンジンのパーツもたくさんあります(JSヒープやコールスタックなど)。JavaScriptの内部に興味がある場合は、さらに調べてみることをお勧めします。V8はオープンソースで、内部でどのように動作するか解説した優れたドキュメントがあります🤖
質問があれば、気軽にお問い合わせください😊
sponsors