ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript 実行メカニズムのサンプルコード分析

JavaScript 実行メカニズムのサンプルコード分析

黄舟
黄舟オリジナル
2017-03-09 14:04:261328ブラウズ

JavaScript 実行メカニズムのサンプル コード分析

簡単な質問から始めます:

<script type="text/javascript">     
alert(i);   
var i = 1;     
</script>

出力結果は未定義です。この現象は「事前解析」と呼ばれます。JavaScript エンジンは var 変数と関数の解析を優先します。意味。事前解析が完了するまでコードは実行されません。ドキュメント ストリームに複数のスクリプト コード セグメント (スクリプト タグで区切られた JS コード、またはインポートされた JS ファイル) が含まれている場合、実行シーケンスは次のとおりです。

ステップ 1、最初のコード セグメントを読み取り、エラーがある場合は、ステップ 2 を実行します。構文エラー (括弧の不一致など) が報告され、ステップ 5

ステップ 3 に進みます。var 変数と関数定義の「事前解析」を実行します (正しい宣言のみが解析されるため、エラーは報告されません)。 )

step4. コード セグメントを実行し、エラーがある場合はエラーを報告します (変数が未定義であるなど)。

step5. 別のコード セグメントがある場合は、次のコード セグメントを読み取り、step2
step6 を繰り返します。上記の分析で多くの問題はすでに説明できますが、私は常に何かが欠けているように感じていました。たとえば、ステップ 3 の「事前解析」とは正確には何ですか?ステップ 4 では、次の例を見てください:

<script type="text/javascript">     
alert(i); // error: i is not defined.     
i = 1;     
</script>

最初の文でエラーが発生するのはなぜですか? JavaScript では変数を未定義にする必要はないのでしょうか?

編集プロセス

本棚の隣で、別世界のような『編集の原理』を開くと、見慣れた、しかし見慣れない空白の中にこんなメモがあった。従来のコンパイル言語の場合、コンパイル手順は字句解析、構文解析、意味チェック、コードの最適化、バイト生成に分かれています。

しかし、インタープリタ型言語の場合は、字句解析と構文解析を通じて構文ツリーが取得された後、解釈と実行を開始できます。

簡単に言えば、字句解析とは、 c = a – b; を次のように変換するなど、文字ストリーム (char ストリーム) をトークン ストリーム (トークン ストリーム) に変換することです。

NAME "c" 
EQUALS  
NAME "a" 
MINUS  
NAME "b" 
SEMICOLON

上記は単なる例です。 、字句解析を確認してください。

「JavaScript の決定版ガイド」の第 2 章では、ECMA-262 にも記載されている字句構造について説明しています。語彙構造は言語の基礎であり、習得は簡単です。字句解析の実装については、別の研究領域であるため、ここでは説明しません。

自然言語を例えとして使用できます。たとえば、英語の段落を単語ごとに中国語に翻訳すると、大量のトークン ストリームが得られます。理解するのが難しいです。さらに翻訳するには文法解析が必要です。次の図は条件文の構文ツリーです。

構文ツリーを構築する際に、if(a { i = 2; } など) が構築できないことが判明した場合、構文はエラーが報告され、コード ブロック全体の解析を終了します。これは、この記事の冒頭のステップ 2 です。

構文分析を通じて構文ツリーを構築した後でも、翻訳された文があいまいな場合があり、さらなる意味チェックが必要になります。従来の強く型付けされた言語の場合、セマンティック チェックの主な部分は、関数の実パラメータと仮パラメータの型が一致するかどうかなどの型チェックです。弱い型付けの言語では、このステップは利用できない場合があります。 JS を読む時間です)。エンジンの実装には、JS エンジンにセマンティック チェック ステップがあるかどうかわかりません。

上記の分析から、JavaScript エンジンには字句解析と構文が必要であることがわかります。これらのコンパイル手順が完了すると (どの言語にもコンパイル プロセスがありますが、インタープリタ言語はバイナリ コードにコンパイルされません)、コードが実行されます。

上記のコンパイル プロセスについては、記事の冒頭ではまだ詳しく説明できません。「事前解析」の一部として、JavaScript コードの実行プロセスを注意深く調査する必要があります。

実行プロセス

Zhou Aiminこれについては、「JavaScript 言語の本質とプログラミングの実践」の第 2 部で非常に注意深く分析しています。以下に私の洞察をいくつか示します。

コンパイルを通じて、JavaScript コードは構文ツリーに変換されます。

さらに実行するには、JavaScript のスコープの仕組みを理解する必要があります。簡単に言えば、JavaScript 変数のスコープは定義時に決定されます。つまり、字句スコープはソース コードに依存し、コンパイラは静的解析を通じてそれを決定できるため、字句スコープは静的スコープとも呼ばれます。ただし、セマンティクスに注意する必要があります。 with と eval は静的技術だけでは実現できません。実際、JS のスコープ機構はレキシカルスコープに非常に近いとしか言​​えません。

JS エンジンは各関数を実行します。インスタンスが作成されると、実行コンテキストが生成されます。実行コンテキストには、内部変数テーブル varDecls、組み込み関数テーブル funDecls、および親参照リスト (注: varDecls などの情報) を保存するために使用される scriptObject 構造体である呼び出しオブジェクトが含まれます。 funDecls は構文解析段階で取得され、関数インスタンスの実行時に構文ツリーに保存されます。この情報は構文ツリーから scriptObject にコピーされます。 scriptObject は、関数インスタンスのライフサイクルと一致する、関数に関連する静的システムのセットです。

lexical scope是JS的作用域机制,还需要理解它的实现方法,这就是作用域链(scope chain)。scope chain是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父级scriptObject中寻找,一直 lookup到全局调用对象(global object)。

当一个函数实例执行时,会创建或关联到一个闭包(closure)。 scriptObject用来静态保存与函数相关的变量表,closure则在执行期动态保存这些变量表及其运行值。closure的生命周期有可能比函数实例长。函数实例在活动引用为空后会自动销毁,closure则要等要数据引用为空后,由JS引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。

别被上面的一堆名词吓住,一旦理解了执行环境、调用对象、闭包、词法作用域、作用域链这些概念,JS语言的很多现象都能迎刃而解。

小结

至此,对于文章开头部分的疑问,可以解释得很清楚了:

step3中所谓的“预解析”,其实是在step2的语法分析阶段完成,并存储在语法树中。当执行到函数实例时,会将varDelcs和funcDecls从语法树中复制到执行环境的scriptObject上。

step4中,未定义变量意味着在scriptObject的变量表中找不到,JS引擎会沿着scriptObject的upvalue往上寻找,如果都没找到,对于写操作i = 1; 最后就会等价为 window.i = 1; 给window对象新增了一个属性。对于读操作,如果一直追溯到全局执行环境的scriptObject上都找不到,就会产生运行期错误。

理解后,雾散花开,天空一片晴朗。

最后,留个问题给大家:

<script type="text/javascript">     
var arg = 1;     
function foo(arg) {     
alert(arg);     
var arg = 2;     
}     
foo(3);     
</script>

请问alert的输出是什么?

以上がJavaScript 実行メカニズムのサンプルコード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。