スクリプトのロードおよび実行順序


265

JavaScriptをHTMLページに含めるには、さまざまな方法があります。次のオプションについて知っています。

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

ハードディスクからのブラウザスクリプト、javascript:onEventURIs および-attributes [ 3 ]を数えないで、JSを実行するためのすでに16の選択肢があり、私は何かを忘れたと確信しています。

私は高速(並列)ロードにそれほど関心がありません。実行順序(ロード順序とドキュメントの順序に依存する場合があります)に興味があります。本当にすべてのケースをカバーする優れた(ブラウザ間の)リファレンスはありますか?たとえば、http://www.websiteoptimization.com/speed/tweak/defer/ はそのうちの6つのみを扱い、ほとんどが古いブラウザをテストします。

ないのではないかと心配しているので、ここに私の具体的な質問を示します。初期化とスクリプトの読み込み用の(外部)ヘッドスクリプトがいくつかあります。次に、本文の終わりに2つの静的なインラインスクリプトを用意します。最初のスクリプトでは、スクリプトローダーが別のスクリプト要素(外部jsを参照)を動的に本文に追加できます。2番目の静的インラインスクリプトは、追加された外部スクリプトのjsを使用したいと考えています。他の人が実行されたことに依存できますか(そしてなぜ:-)?


Steve Souders がブロックせずにスクリプトロードするのを見たことはありますか?今は少し古くなっていますが、特定のスクリプト読み込みテクニックを前提としたブラウザーの動作に関するいくつかの貴重な洞察が含まれています。
Josh Habdas 2017年

回答:


331

スクリプトを動的にロードしていdeferないasync、またはとしてマークしていない場合、スクリプトはページで検出された順序でロードされます。それが外部スクリプトであるかインラインスクリプトであるかは問題ではありません-それらはページで遭遇した順番で実行されます。外部スクリプトの後に来るインラインスクリプトは、それらの前にあるすべての外部スクリプトが読み込まれて実行されるまで保持されます。

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

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

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

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

付きのスクリプトタグdeferは、パーサー全体が完了するまで待機し、でマークさdeferれたすべてのスクリプトを、検出された順に実行します。これにより、相互に依存する複数のスクリプトをとしてマークできますdefer。これらはすべて、ドキュメントパーサーが完了するまで延期されますが、依存関係を維持しながら、発生した順に実行されます。deferスクリプトは、パーサーが完了した後に処理されるキューにドロップされるようなものだと思います。技術的には、ブラウザは、任意の時点で、バックグラウンドでのスクリプトをダウンロードするかもしれないが、彼らが実行したり、パーサーがページを解析し、解析してマークされていない任意のインラインスクリプト実行中に行われた後まで、パーサをブロックしませんdeferかをasync

その記事からの引用は次のとおりです。

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

HTML5仕様の関連部分(新しい準拠ブラウザーの場合)はこちらです。非同期動作については、そこに多くのことが書かれています。明らかに、この仕様は、動作を確認するためにテストが必要になる可能性のある古いブラウザ(または適合しないブラウザ)には適用されません。

HTML5仕様からの引用:

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

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

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

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

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

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

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

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

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

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

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

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

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

エレメントにsrc属性がある場合エレメントは、スクリプトアルゴリズムの準備が開始されたときにスクリプトエレメントのドキュメントのできるだけ早く実行されるスクリプトのセットに追加する必要があります。

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

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


JavaScriptモジュールスクリプトについてはどうtype="module"ですか?

JavaScriptは、次のような構文でモジュールのロードをサポートするようになりました。

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

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

または、src属性:

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

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

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

:ショーはフェッチと、ここで、この記事では、モジュールスクリプトを含むスクリプトの異なる組み合わせの実行ことはかなり便利なタイムラインチャートありますJavascriptのモジュールのロードは


回答ありがとうございます。問題は、スクリプトページに動的に追加されることです。つまり、非同期であると見なされます。それとも<head>でのみ機能しますか?そして、私の経験では、それらはドキュメントの順序で実行されますか?
Bergi、2012年

@Bergi-動的に追加される場合、非同期であり、制御するコードを記述しない限り、実行順序は不確定です。
jfriend00 2012年

ちょうど、Kolinkは反対を述べています...
Bergi

@Bergi-わかりました。非同期スクリプトは不確定な順序で読み込まれるように私の回答を変更しました。それらは任意の順序でロードできます。私があなただったら、Kolinkの観察がいつものようであることを当てにしません。動的に追加されたスクリプトはすぐに実行する必要があり、ロードされるまで他のスクリプトの実行をブロックしなければならないという標準は知りません。私はそれがブラウザに依存し、おそらく環境的要因(スクリプトがキャッシュされているかどうかなど)に依存していることを期待します。
jfriend00

1
@RuudLenders-それはブラウザの実装次第です。ドキュメントの最初の方でスクリプトタグに出会うが、でマークされているとdefer、パーサーは実行を延期しながら、ダウンロードをより早く開始することができます。同じホストから多数のスクリプトがある場合、ダウンロードをより早く開始すると、ページが待機している(帯域幅を奪い合う)同じホストからの他のダウンロードが実際に遅くなる可能性があることに注意してください(そうではありませんdefer)。これは両刃の剣かもしれません。
jfriend00

13

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

この事実をテストするには:

// 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>。現在のタグを終了して新しいタグを開始する場合にのみ、外部スクリプトを含めて、それらの関数と変数にアクセスできます。


その動作を確認できます。ただし、フィードバックページには、test.phpがキャッシュされている場合にのみ機能するというヒントがあります。これに関する仕様/参照リンクを知っていますか?
Bergi、2012年

4
link.jsはブロックしていません。phpに似たスクリプトを使用して、長いダウンロード時間をシミュレートします。
1983年

14
この答えは間違っています。「動的に追加されたスクリプトは、ドキュメントに追加されるとすぐに実行される」とは限りません。これが正しい場合もあります(たとえば、Firefoxの古いバージョンの場合など)が、通常はそうではありません。jfriend00の回答で述べたように、実行順序は確定されていません。
Fabio Beltramini

1
スクリプトがインラインであるかどうかに関係なく、スクリプトがページに表示される順序で実行されることは意味がありません。では、Googleタグマネージャーのスニペットやその他の多くのコードに、ページ内の他のすべてのスクリプトタグの上に新しいスクリプトを挿入するコードがあるのはなぜですか。上記のスクリプトがすでに確実にロードされている場合、これを行うのは意味がありませんか?または私は何かが欠けていますか?
user3094826


2

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

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'])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.