web-dev-qa-db-ja.com

スクリプトの順番を読み込んで実行する

HTMLページにJavaScriptを含めるには、さまざまな方法があります。私は以下の選択肢について知っています:

  • インラインコードまたは外部URIからロード
  • <head>または<body>タグに含まれる[ 12 ]
  • deferまたはasync属性を持たない(外部スクリプトのみ)
  • 静的なソースに含まれるか、他のスクリプトによって動的に追加されます(異なる解析状態で、異なるメソッドを使用)。

ハードディスクからのブラウザスクリプト、javascript:URIおよびonEvent-attributes []を除いて、JSを実行するための選択肢は既に16ありますが、忘れてしまいました。

私は高速(並列)ロードについてはそれほど心配していません、実行順序についてもっと興味があります(それはロード順序と ドキュメント順序 に依存するかもしれません)。 (クロスブラウザ)本当にすべてのケースを網羅した参考文献がありますか?http://www.websiteoptimization.com/speed/Tweak/defer/ それらのうちの6つだけを扱い、ほとんど古いブラウザをテストします。

私がそうでないことを恐れているので、ここに私の特定の質問があります:私は初期化とスクリプトロードのためにいくつかの(外部の)ヘッドスクリプトを持っています。それから本体の最後に2つの静的なインラインスクリプトがあります。 1つ目は、スクリプトローダーが本体に別のスクリプト要素(外部jsを参照する)を動的に追加することを可能にします。静的インラインスクリプトの2番目は、追加された外部スクリプトのjsを使用したいと考えています。他の人が処刑されたことに頼ることはできますか(そしてなぜ:-)?

232
Bergi

スクリプトを動的にロードしていない場合、またはdefername__またはasyncname__としてマークしている場合、スクリプトはページで検出された順序でロードされます。外部スクリプトであるかインラインスクリプトであるかは関係ありません。ページで検出された順序で実行されます。外部スクリプトの後に来るインラインスクリプトは、それらの前に来るすべての外部スクリプトがロードおよび実行されるまで保持されます。

非同期スクリプトは(非同期としての指定方法に関係なく)予測不能な順序でロードおよび実行されます。ブラウザはそれらを並行してロードし、好きな順序で自由に実行できます。

複数の非同期処理の間に予測可能な順序はありません。予測可能な順序が必要な場合は、非同期スクリプトからのロード通知を登録し、適切なものがロードされたときにjavascript呼び出しを手動で順序付けすることにより、コーディングする必要があります。

スクリプトタグが動的に挿入される場合、実行順序の動作はブラウザによって異なります。 Firefoxの動作は このリファレンス記事 で確認できます。一言で言えば、Firefoxの新しいバージョンでは、スクリプトタグが設定されていない限り、動的に追加されたスクリプトタグがデフォルトで非同期に設定されます。

asyncname__を含むスクリプトタグは、ロードされるとすぐに実行できます。実際、ブラウザーは、パーサーを他の操作から一時停止し、そのスクリプトを実行する場合があります。そのため、ほぼいつでも実行できます。スクリプトがキャッシュされた場合、ほとんどすぐに実行される可能性があります。スクリプトのロードに時間がかかる場合は、パーサーの完了後に実行される可能性があります。 asyncname__で覚えておくべき1つのことは、いつでも実行でき、その時間は予測できないことです。

defername__を含むスクリプトタグは、パーサー全体が完了するまで待機してから、defername__でマークされたすべてのスクリプトを発生順に実行します。これにより、相互に依存する複数のスクリプトをdefername__としてマークできます。これらはすべて、ドキュメントパーサーが完了するまで延期されますが、依存関係を保持した状態で実行されます。 defername__は、スクリプトがキューにドロップされ、パーサーが完了した後に処理されるように思えます。技術的には、ブラウザーはいつでもバックグラウンドでスクリプトをダウンロードしている可能性がありますが、パーサーがページの解析とdefername__またはasyncname__のマークが付いていないインラインスクリプトの解析と実行が完了するまで、パーサーは実行またはブロックされません。

その記事からの引用です:

スクリプトが挿入されたスクリプトは、IEおよびWebKitでは非同期的に実行されますが、Operaおよび4.0より前のFirefoxでは同期的に実行されます。

HTML5仕様の関連部分(より新しい準拠ブラウザ向け)は here です。非同期動作について多くのことが書かれています。明らかに、この仕様は、動作を判断するためにテストする必要があると思われる古いブラウザ(または不正な確認を行うブラウザ)には適用されません。

HTML5仕様からの引用:

次に、状況を説明する次のオプションの最初に従う必要があります。

要素にsrc属性があり、要素にdefer属性があり、要素に「parser-inserted」というフラグが付けられていて、要素にasync属性がない場合要素を作成したパーサーのドキュメントに関連付けられたドキュメントの解析が完了したときに実行されるスクリプトのリストの最後に要素を追加する必要があります。

フェッチアルゴリズムが完了すると、ネットワークタスクソースがタスクキューに配置するタスクは、要素の「パーサー実行準備完了」フラグを設定する必要があります。パーサーはスクリプトの実行を処理します。

要素にsrc属性があり、要素に「パーサー挿入」のフラグが立てられていて、要素に非同期属性がない場合要素を作成したパーサーのドキュメントの保留中の解析ブロックスクリプトです。 (このようなスクリプトは、一度に1つのドキュメントにつき1つしか存在できません。)

フェッチアルゴリズムが完了すると、ネットワークタスクソースがタスクキューに配置するタスクは、要素の「パーサー実行準備完了」フラグを設定する必要があります。パーサーはスクリプトの実行を処理します。

要素にsrc属性がなく、要素に「パーサー挿入」のフラグが立てられ、スクリプト要素を作成したHTMLパーサーまたはXMLパーサーのドキュメントにスタイルがある場合スクリプトをブロックしているシート要素は、要素を作成したパーサーのドキュメントの保留中の構文解析ブロックスクリプトです。 (このようなスクリプトは、一度に1つのドキュメントにつき1つしか存在できません。)

要素の「パーサー実行の準備完了」フラグを設定します。パーサーはスクリプトの実行を処理します。

要素にsrc属性があり、非同期属性がなく、「force-async」フラグが設定されていない場合要素を追加する必要がありますスクリプトアルゴリズムの準備が開始された時点で、スクリプト要素のドキュメントに関連付けられたできるだけ早く順番に実行されるスクリプトのリストの最後まで。

フェッチアルゴリズムが完了すると、ネットワークタスクソースがタスクキューに配置するタスクは、次の手順を実行する必要があります。

要素が、上で追加されたできるだけ早く順番に実行されるスクリプトのリストの最初の要素ではない場合、要素は準備ができているが、スクリプトをまだ実行せずにこれらの手順を中止します。

実行:このスクリプトリストの最初のスクリプト要素に対応するスクリプトブロックを、できるだけ早く順番に実行します。

できるだけ早く順番に実行されるスクリプトのリストから最初の要素を削除します。

できるだけ早く順番に実行されるこのスクリプトのリストがまだ空ではなく、最初のエントリがすでに準備完了としてマークされている場合、実行というラベルの付いたステップに戻ります。

要素にsrc属性がある場合要素は、スクリプト要素のDocumentのできるだけ早く実行されるスクリプトのセットに追加する必要があります。スクリプトの準備アルゴリズムが開始された時間。

フェッチングアルゴリズムが完了すると、ネットワークタスクソースがタスクキューに配置するタスクは、スクリプトブロックを実行し、できるだけ早く実行するスクリプトのセットから要素を削除する必要があります。

それ以外の場合他のスクリプトがすでに実行されている場合でも、ユーザーエージェントはスクリプトブロックをすぐに実行する必要があります。


Javascriptモジュールスクリプト、type="module"はどうですか?

Javascriptは、次のような構文でモジュールをロードできるようになりました。

<script type="module">
  import {addTextToBody} from './utils.mjs';

  addTextToBody('Modules are pretty cool.');
</script>

または、srcname__属性を使用:

<script type="module" src="http://somedomain.com/somescript.mjs">
</script>

すべて、type="module"を持つスクリプトには、defername__属性が自動的に与えられます。これは、ページの他のロードと並行して(インラインでない場合)それらをダウンロードしてから、パーサーの実行後に順番に実行します。

モジュールスクリプトには、asyncname__属性を指定することもできます。この属性は、パーサーが完了するまで待機せず、他のスクリプトに関連する特定の順序でasyncname__スクリプトを実行するのを待たずに、インラインモジュールスクリプトをできるだけ早く実行します。

この記事のモジュールスクリプト Javascript Module Loading を含む、スクリプトのさまざまな組み合わせのフェッチと実行を示す非常に便利なタイムラインチャートがあります。

293
jfriend00

ブラウザはスクリプトを見つけた順序でスクリプトを実行します。外部スクリプトを呼び出すと、スクリプトが読み込まれて実行されるまでページがブロックされます。

この事実を検証するには

// file: test.php
sleep(10);
die("alert('Done!');");

// HTML file:
<script type="text/javascript" src="test.php"></script>

動的に追加されたスクリプトは、ドキュメントに追加されるとすぐに実行されます。

この事実を検証するには

<!DOCTYPE HTML>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <script type="text/javascript">
        var s = document.createElement('script');
        s.type = "text/javascript";
        s.src = "link.js"; // file contains alert("hello!");
        document.body.appendChild(s);
        alert("appended");
    </script>
    <script type="text/javascript">
        alert("final");
    </script>
</body>
</html>

アラートの順番は「追加」 - >「こんにちは」です。 - > "決勝"

スクリプト内でまだ到達していない要素にアクセスしようとすると(例:<script>do something with #blah</script><div id="blah"></div>)、エラーが発生します。

全体として、はい、外部スクリプトをインクルードしてからそれらの関数と変数にアクセスすることができます。ただし、現在の<script>タグを終了して新しいタグを開始する場合に限ります。

11

@ addyosmani によるすばらしいまとめ

enter image description here

https://addyosmani.com/blog/script-priorities/ から恥知らずにコピー

3
Dehan de Croos

多くのオプションをテストした後、私は以下の簡単な解決策がそれらが現代のすべてのブラウザで追加される順序で動的にロードされたスクリプトをロードすることであることがわかりました

loadScripts(sources) {
    sources.forEach(src => {
        var script = document.createElement('script');
        script.src = src;
        script.async = false; //<-- the important part
        document.body.appendChild( script ); //<-- make sure to append to body instead of head 
    });
}

loadScripts(['/scr/script1.js','src/script2.js'])
0
Flion