前回は、JavaScript エンジンの解析メカニズムから JavaScript の動作原理を説明しました。ここでは、より鮮明な例を使用して、ページ上の JavaScript コードの実行順序を説明します。 JavaScript エンジンの動作メカニズムが基礎的な動作に属しているため、比較的奥深い場合、JavaScript コードの実行順序はより鮮明になります。これは、JavaScript コードの実行順序を直感的に感じることができるためです。はより複雑なので、JavaScript 言語を詳しく説明する前に、JavaScript 言語のプロファイルを作成することも必要です。
1.1 HTML ドキュメント フローの順序で JavaScript コードを実行する
まず最初に、ブラウザでの HTML ドキュメントの解析プロセスは次のとおりであることを読者は知っておく必要があります。ブラウザはドキュメント フローに従って、ページ構造と情報を上から下に徐々に解析します。埋め込みスクリプトとしての JavaScript コードも HTML ドキュメントのコンポーネントとしてカウントされる必要があるため、読み込み時の JavaScript コードの実行順序も、スクリプト タグ <script> の出現順序に基づいて決定されます。たとえば、以下のドキュメント ページを参照すると、コードが上から下まで段階的に解析されていることがわかります。
次のようにコードをコピーします。
<スクリプト>
alert("トップスクリプト");
</script>
<html><ヘッド>
<スクリプト>
alert("ヘッドスクリプト");
</script>
<タイトル></タイトル>
</head>
<本文>
<スクリプト>
alert("ページスクリプト");
</script>
</body></html>
<スクリプト>
alert("下部のスクリプト");
</script>
外部 JavaScript ファイルのスクリプトがスクリプト タグ <script> の src 属性を通じてインポートされる場合、そのスクリプトもそのステートメントが表示される順序で実行され、実行プロセスはドキュメントの読み込みの一部となります。外部JavaScriptファイルなので実行が遅れることはありません。たとえば、上記のドキュメントのヘッド領域とボディ領域にあるスクリプトを外部 JavaScript ファイルに移動し、src 属性を通じてインポートします。ページドキュメントのプレビューを続けると、同じ実行順序が表示されます。
次のようにコードをコピーします。
<スクリプト>
alert("トップスクリプト");
</script>
<html>
<頭>
<script src="//www.VeVB.COm/head.js"></script>
<タイトル></タイトル>
</head>
<本文>
<script src="//www.VeVB.COm/body.js"></script>
</body>
</html>
<スクリプト>
alert("下部のスクリプト");
</script>
1.2 プリコンパイルと実行順序の関係
JavaScript では、関数は JavaScript の最初のタイプです。関数を記述するとき、実際には関数型のエンティティを作成しているだけです。
ちょうど次の形式で書くことができます:
次のようにコードをコピーします。
functionHello()
{
アラート("こんにちは");
}
こんにちは();
varHello = 関数()
{
アラート("こんにちは");
}
こんにちは();
実際、それらはすべて同じです。 しかし、関数を変更すると、非常に奇妙な問題が発生します。
次のようにコードをコピーします。
<scripttype="text/javascript">
functionHello() {
アラート("こんにちは");
}
こんにちは();
functionHello() {
アラート("Hello World");
}
こんにちは();
</script>
次のような結果が表示されます。 Hello World が 2 回続けて出力されます。
私たちが想像していたHello and Hello Worldではなく。
これは、JavaScript が完全に解釈されて順番に実行されるわけではないため、プリコンパイル処理中に、定義された関数が最初に実行され、デフォルト値は未定義になります。プログラムの実行効率。
言い換えれば、上記のコードは実際には JS エンジンによって次の形式にプリコンパイルされます。
次のようにコードをコピーします。
<scripttype="text/javascript">
varHello = function() {
アラート("こんにちは");
}
こんにちは = function() {
アラート("Hello World");
}
こんにちは();
こんにちは();
</script>
上記のコードから、関数もデータであり変数であることが明確にわかります。また、「関数」に値を代入 (再割り当て) することもできます。
もちろん、この状況を防ぐために、次のようにすることもできます。
次のようにコードをコピーします。
<scripttype="text/javascript">
functionHello() {
アラート("こんにちは");
}
こんにちは();
</script>
<scripttype="text/javascript">
functionHello() {
アラート("Hello World");
}
こんにちは();
</script>
このようにプログラムは 2 つのセクションに分割されており、JS エンジンはそれらを結合しません。
JavaScript エンジンはスクリプトを解析する際、プリコンパイル中に宣言されたすべての変数と関数を処理します。
次のことを実行します。
1. 実行前に、「プリコンパイル」と同様の操作が実行されます。まず、現在の実行環境でアクティブなオブジェクトが作成され、var で宣言された変数がアクティブなオブジェクトの属性として設定されますが、この時点では、これらの変数の割り当ては未定義になり、 function で定義された関数もアクティブなオブジェクトのプロパティとして追加され、それらの値は関数の定義とまったく同じになります。
2. 解釈および実行フェーズ中に、変数を解析する必要がある場合、変数が見つからず、実行環境の所有者がプロトタイプ属性を持っている場合は、まず現在の実行環境のアクティブなオブジェクトから検索されます。プロトタイプ チェーンから検索されます。それ以外の場合は、スコープ チェーンに従って検索されます。 var a = ... などのステートメントに遭遇すると、対応する変数に値が割り当てられます (注: 変数の割り当ては解釈および実行フェーズ中に完了します。これより前に変数が使用された場合、その値はしたがって、JavaScript インタープリターが次のスクリプトを実行してもエラーは報告されないように見えます。
次のようにコードをコピーします。
alert(a); // 戻り値は未定義です
変数 a =1;
アラート(a); // 戻り値 1
変数宣言はプリコンパイル時に処理されるため、実行中にすべてのコードから参照できます。ただし、上記のコードを実行すると、プロンプトされる値が 1 ではなく、未定義であることもわかります。これは、変数の初期化プロセスがプリコンパイルではなく実行中に発生するためです。実行中に、JavaScript インタープリターはコードを順番に解析します。コードの前の行で変数に値が割り当てられていない場合、JavaScript インタープリターはデフォルト値の unknown を使用します。変数 a には 2 行目で値が割り当てられているため、コードの 3 行目では、変数 a の値が未定義ではなく 1 であることが示されます。
同様に、次の例では、関数が宣言される前に関数を呼び出すことは正当であり、正しく解析できるため、戻り値は 1 になります。
次のようにコードをコピーします。
f(); // 関数を呼び出し、値 1 を返す
関数 f(){
アラート(1);
}
ただし、関数が次のように定義されている場合、JavaScript インタプリタによって構文エラーが表示されます。
次のようにコードをコピーします。
f(); // 関数を呼び出して構文エラーを返す
var f = 関数(){
アラート(1);
}
これは、上記の例で定義された関数は変数 f に値としてのみ割り当てられるため、プリコンパイル期間中、JavaScript インタープリターは変数 f の宣言のみを処理し、変数 f の値のみを処理できます。実行中に を押すと、代入が連続して実行されると、オブジェクト f が見つからないことを示す構文エラーが当然発生します。
さようなら、いくつかの例:
次のようにコードをコピーします。
<script type="text/javascript">
/*プリコンパイルプロセス中、func はウィンドウ環境のアクティブなオブジェクトの属性であり、値は関数であり、未定義の値をカバーします*/
アラート(関数); //関数 func(){アラート("こんにちは!")}
var func = "これは変数です"
関数 func(){
アラート(「こんにちは!」)
}
/*実行中に var が検出され、「これは変数です」に再割り当てされました*/
alert(func); //これは変数です
</script>
次のようにコードをコピーします。
<script type="text/javascript">
変数名 = "feng" 関数 func();
{
/*まず、func 環境で unknown に name を割り当て、次に、実行中に func 環境でアクティブなオブジェクトの name 属性を検索します。この時点では、値は unfined にプリコンパイルされているため、出力は unfined ではありません。フォン */
アラート(名前); //未定義の変数名 = "JSF";
アラート(名前); //JSF
}
関数();
アラート(名前);
//フェン
</script>
変数と関数の宣言はドキュメント内のどこにでも記述できますが、すべての JavaScript コードの前にグローバル変数と関数を宣言し、変数を初期化して代入することをお勧めします。関数内では、変数は最初に宣言されてから参照されます。
1.3 JavaScript コードをブロックで実行する
いわゆるコード ブロックは、<script> タグで区切られたコード セグメントです。たとえば、以下の 2 つの <script> タグは 2 つの JavaScript コード ブロックを表します。
次のようにコードをコピーします。
<スクリプト>
// JavaScript コードブロック 1
変数 a =1;
</script>
<スクリプト>
// JavaScript コード ブロック 2
関数 f(){
アラート(1);
}
</script>
JavaScript インタープリターはスクリプトを実行する際、ブロック単位で実行します。平たく言えば、ブラウザが HTML ドキュメント ストリームを解析するときに <script> タグに遭遇すると、JavaScript インタプリタはコード ブロックがロードされるまで待機し、まずコード ブロックをプリコンパイルしてから実行します。実行後、ブラウザーは以下の HTML ドキュメント ストリームの解析を続け、JavaScript インタープリターはコードの次のブロックを処理する準備が整います。
JavaScript はブロック単位で実行されるため、後続のブロックで宣言された変数や関数を JavaScript ブロック内で呼び出すと、構文エラーが表示されます。たとえば、JavaScript インタプリタが次のコードを実行すると、変数 a が未定義でオブジェクト f が見つからないことを示す構文エラーが表示されます。
次のようにコードをコピーします。
<スクリプト>
// JavaScript コードブロック 1
アラート(a);
f();
</script>
<スクリプト>
// JavaScript コード ブロック 2
変数 a =1;
関数 f(){
アラート(1);
}
</script>
JavaScript はブロック内で実行されますが、異なるブロックは同じグローバル スコープに属します。これは、ブロック間の変数や関数を共有できることを意味します。
1.4 イベントメカニズムを使用して JavaScript の実行順序を変更する
JavaScript はコードをチャンクに分けて処理し、HTML ドキュメント フローの解析順序に従うため、上記の例ではこのような構文エラーが発生します。ただし、ドキュメント ストリームがロードされると、再度アクセスしてもこのようなエラーは発生しません。たとえば、コードの 2 番目のブロックの変数と関数にアクセスするコードがページ初期化イベント関数に配置されている場合、構文エラーは発生しません。
次のようにコードをコピーします。
<スクリプト>
// JavaScript コードブロック 1
window.onload = function(){ // ページ初期化イベント処理関数
アラート(a);
f();
}
</script>
<スクリプト>
// JavaScript コード ブロック 2
変数 a =1;
関数 f(){
アラート(1);
}
</script>
セキュリティ上の理由から、通常、JavaScript コードの実行はページの初期化後にのみ許可されます。これにより、JavaScript の実行に対するネットワーク速度の影響を回避でき、HTML ドキュメント フローによって引き起こされる JavaScript の実行の制限も回避できます。
知らせ
ページ内に複数の windows.onload イベント ハンドラーがある場合、最後のものだけが有効になります。この問題を解決するには、すべてのスクリプトまたは呼び出し関数を同じ onload イベント ハンドラーに含めることができます。
次のようにコードをコピーします。
window.onload = function(){
f1();
f2();
f3();
}
このように、onload イベント ハンドラーで関数の呼び出し順序を調整するだけで、関数の実行順序を変更できます。
ページ初期化イベントに加えて、マウス イベント、キーボード イベント、クロック トリガーなどのさまざまなインタラクティブ イベントを通じて JavaScript コードの実行順序を変更することもできます。詳細な説明については、第 14 章を参照してください。
1.5 JavaScript出力スクリプトの実行順序
JavaScript 開発では、JavaScript スクリプトを出力するためにドキュメント オブジェクトの write() メソッドがよく使用されます。では、これらの動的出力スクリプトはどのように実行されるのでしょうか?例えば:
次のようにコードをコピーします。
document.write('<script type="text/javascript">');
document.write('f();');
document.write('function f(){');
document.write('alert(1);');
document.write('}');
document.write('</script>');
上記のコードを実行すると、次のことがわかります。 document.write() メソッドは、最初に、スクリプトが配置されているドキュメントの場所に出力スクリプト文字列を書き込みます。 document.write() が配置されているドキュメントの内容を解析した後、ブラウザは document.write() 出力コンテンツの解析を続け、その後、後続の HTML ドキュメントを順番に解析します。つまり、JavaScript スクリプトが出力したコード列は、出力直後に実行されます。
document.write() メソッドを使用して出力される JavaScript スクリプト文字列は、同時に出力される <script> タグ内に配置する必要があることに注意してください。そうしないと、JavaScript インタプリタはこれらの正当な JavaScript コードを認識できず、ページドキュメントでは通常の文字列として表示されます。たとえば、次のコードは JavaScript コードを実行する代わりに表示します。
次のようにコードをコピーします。
document.write('f();');
document.write('function f(){');
document.write('alert(1);');
document.write(');');
ただし、document.write() メソッドを介してスクリプトを出力および実行する場合には、一定のリスクが伴います。これは、JavaScript エンジンによってスクリプトが異なる順序で実行され、解析中にブラウザによってバグが発生する可能性があるためです。
Ø 問題 1: document.write() メソッドを通じてインポートされた外部 JavaScript ファイル内で宣言された変数または関数が見つかりません。たとえば、以下のサンプルコードを見てください。
次のようにコードをコピーします。
document.write('<script type="text/javascript" src="//www.VeVB.COm/test.js">
</script>');
document.write('<script type="text/javascript">');
document.write('alert(n);'); // IE は変数 n が見つからないことを通知します
document.write('</script>');
alert(n+1); // すべてのブラウザは、変数 n が見つからないことを通知します。
外部JavaScriptファイル(test.js)のコードは以下のとおりです。
次のようにコードをコピーします。
var n = 1;
別のブラウザでテストすると、構文エラーが発生し、変数 n が見つかりません。つまり、JavaScript コード ブロック内で、このコード ブロックの document.write() メソッドを使用して、スクリプト出力にインポートされた外部 JavaScript ファイルに含まれる変数にアクセスすると、構文エラーが表示されます。同時に、IE ブラウザの場合、スクリプトだけでなく出力スクリプトでも、外部 JavaScript ファイルにインポートされた出力変数が見つからないというメッセージが表示されます (式は少し長くて複雑ですが、理解できない読者は、上記のコードを実行してみると理解できます)。
Ø 質問 2: JavaScript エンジンが異なると、出力外部インポート スクリプトの実行順序が若干異なります。たとえば、以下のサンプルコードを見てください。
次のようにコードをコピーします。
<script type="text/javascript">
document.write('<script type="text/javascript" src="http://shaozhuqing.com/test1.js">
</script>');
document.write('<script type="text/javascript">');
document.write('alert(2);')
document.write('alert(n+2);');
document.write('</script>');
</script>
<script type="text/javascript">
アラート(n+3);
</script>
外部 JavaScript ファイル (test1.js) のコードを以下に示します。
次のようにコードをコピーします。
var n = 1;
アラート(n);
IE ブラウザでの実行シーケンスを図 1-6 に示します。
図 1-6 IE 7 ブラウザによって表示される実行シーケンスと構文エラー
DOM 標準に準拠したブラウザでの実行シーケンスは、IE ブラウザでの実行シーケンスとは異なり、構文エラーはありません。図 1-7 は、Firefox 3.0 ブラウザでの実行シーケンスを示しています。
図 1-7 Firefox 3 ブラウザの実行シーケンスと構文エラーのプロンプト
さまざまなブラウザのさまざまな実行順序と考えられるバグを解決します。出力スクリプトを使用してインポートされたすべての外部ファイルを独立したコード ブロックに配置できるため、上で紹介した JavaScript コード ブロックの実行順序に従ってこの問題を回避できます。たとえば、上記の例の場合、次のように設計できます。
次のようにコードをコピーします。
<script type="text/javascript">
document.write('<script type="text/javascript" src="//www.VeVB.COm/test1.js"></script>');
</script>
<script type="text/javascript">
document.write('<script type="text/javascript">');
document.write('alert(2);') ; // ヒント 2
document.write('alert(n+2);'); // ヒント 3
document.write('</script>');
アラート(n+3); // ヒント 4
</script>
<script type="text/javascript">
アラート(n+4); // ヒント 5
</script>
このように、上記のコードは異なるブラウザで順番に実行でき、出力順序は 1、2、3、4、5 になります。問題の理由は、インポートされた出力スクリプトと現在の JavaScript コード ブロックの間の矛盾です。別々に出力した場合は競合しません。