JavaScript運行メカニズムを探る


簡単な問題から話す:

  
  
  
  
  1. <script type="text/javascript">     
  2. alert(i);   
  3. var i = 1;     
  4. </script>    
出力結果はundefinedで、この現象は「前解析」と呼ばれます.JavaScriptエンジンはvar変数とfunction定義を優先的に解析します.事前解析が完了したらコードが実行されます.一つの文書ストリームに複数のスクリプトコードセグメントが含まれている場合(scriptタグで区切られたjsコードまたは導入されたjsファイル).
 
運転順序は:
step 1.最初のコードセグメントstep 2を読み込みます.文法解析を行います.間違ったら文法エラー(括弧不一致など)を報告し、Step 5 step 3にジャンプします.var変数とfunction定義を「事前解析」します.step 4.コードセグメントを実行し、エラーが発生した場合(変数未定義など)Step 5.次のコードがあれば、次のコードセグメントを読み込み、Step 2 Step 6を繰り返します.上の分析を終わらせて、多くの問題を説明できますが、いつも足りない点があります.例えばStep 3で「解析」とはどういうことですか?ステップ4では、次の例を見ます.

  
  
  
  
  1. <script type="text/javascript">     
  2. alert(i); // error: i is not defined.     
  3. i = 1;     
  4. </script>    
 
なぜ最初の文が間違っていますか?JavaScriptでは、変数は定義されなくてもいいですか?
コンパイルプロセス
時間は白馬が隙間を過ぎるように、書棚のそばで隔世のように「コンパイル原理」を開いて、慣れていて見知らぬ空白のところにこのようなメモがあります.伝統的なコンパイル型言語にとって、コンパイルのステップは、語法分析、文法分析、語義検査、コードの最適化とバイトの生成に分けられます.しかし、解釈型言語にとっては、語法分析と文法分析によって文法ツリーを得ると、解釈実行が開始されます.簡単に言えば、語法解析は、c=a-bなどの記号ストリームに変換することである.変換: 

  
  
  
  
  1. NAME "c" 
  2. EQUALS  
  3. NAME "a" 
  4. MINUS  
  5. NAME "b" 
  6. SEMICOLON  
上記は単なる例ですが、Lexical Analysis.『JavaScript権威の手引き』の第2章をご覧ください.語法の構造(Lexical Structure)を説明します.ECMA-622にも記述があります.語法の構造は言語の基礎であり、把握しやすいです.語法分析の実現は別の研究分野です.ここでは探究しません.自然言語を持って類比してもいいです.語法分析は一対一の硬性翻訳です.例えば、英語を一語ずつ中国語に訳して、得たのは記号の流れです.まだ理解しにくいです.さらに、文法分析が必要です.次の図は条件文の文法ツリーです.文法ツリーを作る時、if(a{i=2;)のような構造ができないと発見されました.文法の誤りを報告して、コードブロック全体の解析を終了します.これは本稿の冒頭部分のステップ2です.文法分析を通じて、文法ツリーを構成した後、翻訳された文はまたあいまいなところがあるかもしれません.次はさらに語義検査が必要です.伝統的な強いタイプの言語にとって、語義検査の主要な部分はタイプ検査です.例えば、関数の実参とモダリティのタイプが合っていますか?弱いタイプの言語にはこのステップはないかもしれません.(精力が有限で、JSのエンジンの実現を見る時間がないので、JSエンジンにセマンティックチェックがあるかどうかは確認できません.)上の分析から分かるように、JavaScriptエンジンには必ず語法分析と文法分析があり、その後も語義検査、コード最適化などのステップがあるかもしれません.これらのコンパイルステップが完了したら(どの言語にもコンパイルプロセスがあります.ただし、解釈型語はバイナリコードにコンパイルされていません.)コードの実行を開始します.上のコンパイルプロセスは、まだ文章の先頭部分の「事前解析」をより深く説明することができません.JavaScriptコードの実行過程を詳しく調べなければなりません.
実行プロセス
周愛民は『JavaScript言語の精髄とプログラミングの実践』の第二部分において、これに対して非常に詳しく分析しています.以下は私のいくつかの悟りです.コンパイルを通じて、JavaScriptコードはすでに文法ツリーに翻訳されています.すぐに文法ツリーに従って実行します.さらに実行過程は、JavaScriptの作用領域メカニズムを理解してください.JavaScriptはワードスコープを採用しています.(lexcical scope).分かりやすく言えば、JavaScript変数のスコープは、実行時に決定されるのではなく、すなわち用語のスコープはソースコードによって決まります.コンパイラは静的な分析によって決定されます.したがって、動作領域も静的なスコープと呼ばれます.しかし、withとevalの意味は、静的技術だけでは実現できないので、実際には、JSの作用ドメイン機構はlexical scopeに非常に近いとしか言えません.JSエンジンは、各関数のインスタンスを実行する際に、実行環境を作成します.execution contextには呼び出し対象が含まれています.呼び出し対象は、内部変数テーブルvarDecls、埋め込み関数テーブルfunDecls、親引用リストuplvalueなどの構文解析構造を保存するためのscriptObject構造です.(注意:varDeclsやfunDeclsなどの情報は、構文解析段階ですでに得られており、構文ツリーに保存されます.関数実行時には、これらの情報を構文ツリーからscriptebjectにコピーします.)scriptObjectは関数に関連する静的なシステムであり、関数のインスタンスのライフサイクルと一致しています.lexical scopeはJSの作用ドメイン機構であり、また、その実現方法を理解する必要があります.これは作用ドメインチェーンです.scope chainはname lookupメカニズムで、まず現在実行されている環境のscriptObjectで探していますが、見つけられなかった場合は、uplvalueに沿って父クラスのscript Objectに探して、lookupはグローバル呼び出しの対象(global oject)になります.関数のインスタンスが実行されると、クローズドバッグが作成されます.scriptObjectは関数に関する変数テーブルを静的に保存するために使用されます.closureは実行期間中にこれらの変数表とその動作値を動的に保存します.クロージングのライフサイクルは関数のインスタンスより長い可能性があります.関数のインスタンスは活動参照が空になったら自動的に廃棄されます.クローズアップはデータが空になったらJSエンジンで回収されます.(場合によっては自動的に回収されないので、メモリが漏れてしまうことがあります).上の名詞の山のように驚いてはいけません.実行環境、呼び出し対象、クローズド、品詞作用領域、作用ドメインチェーンなどの概念を理解すれば、JS言語の多くの現象は解決されます.
結び目
ここで、文章の冒頭の疑問について、はっきりと説明できます.Step 3のいわゆる「前解析」です.は、step 2の構文解析段階で完了し、文法ツリーに保存されています.関数のインスタンスを実行すると、varDelcsとfuncDeclsを文法ツリーから実行環境のscriptObjectにコピーします.step 4では、定義されていない変数は、scriptObjectの変数テーブルに見つけられません.JSエンジンは、scriptObjectに沿っています.操作i=1を書きます.最後はwindow.i=1に相当します.windowオブジェクトに属性が追加されました.読み操作については、グローバル実行環境にまで遡るscriptObjectでは見つけられないと、運行期間のエラーが発生します.分かりました.霧が散って花が咲き、空が晴れています.最後に、問題を残します.

  
  
  
  
  1. <script type="text/javascript">     
  2. var arg = 1;     
  3. function foo(arg) {     
  4. alert(arg);     
  5. var arg = 2;     
  6. }     
  7. foo(3);     
  8. </script>   
 
すみません、alertの出力は何ですか?