私がscriptタグについて知っていること全て : 知られていない属性や実行順序など

もうすでにご存じだとは思いますが、scriptタグはWebページで実行されるJavaScriptを特定するために使用されます。scriptタグはJavaScriptを直接含むことも、スクリプトがロードされるべきURLを指すこともできます。

scriptタグは書かれている順番で実行される

次のコードを読めば、直感的に分かるでしょう。

<script>
  var x = 3;
</script>
<script>
  alert(x);
  // Will alert '3';
</script>

外部リソースを操作する時、次のようなコードになりますが、前のコードほど直感的ではありません(だからと言って真実ではないとは限りません)。

<script src="//typekit.com/fj3j1j2.js"></script>

<!-- This second script won’t execute until typekit has executed, or timed out -->
<script src="//my.site/script.js"></script>

ローカルスクリプトとリモートスクリプトを組み合わせても同様に操作することができます。

機能的には、Webページの前の部分で重いスクリプトのロードがあると、サイトの表示が明らかに遅くなることを意味します。さらに、ページの最後の方で表示されるスクリプトは、それまでに存在するされたスクリプトの動作に依存することを意味します。

先行する全てのscriptタグがロードされ実行されるまで、ページ上の要素は表示されません。つまり、パフォーマンスへの悪影響を覚悟する気があれば、ロードする前の微調整する際に自由に突飛なことをすることができるのです。

しかしながら、document.appendChildなどを使用してページをロードした後にscriptタグをDOMに追加した場合は例外となります。この場合、
ブラウザがこれらのタグをロードするタイミングを順不同に決定します。

scriptタグが実行されると、DOMにあるscriptタグより上のものは全て利用可能である(しかしそれより下のものは利用不可)

<html>
  <head>
    <script>
      // document.head is available
      // document.body is not!
    </script>
  </head>
  <body>
    <script>
      // document.head is available
      // document.body is available
    </script>
  </body>
</html>

HTMLパーサが、遭遇するJavaScriptを実行しながらドキュメントのタグからタグへと移動していると思ってください。つまり、あなたのscriptタグより前にオープンタグがあれば、DOMノードはJavaScriptによって利用可能になります(querySelectorAlljQueryなどを通して)。

これは必然的に、(Web上で)使用するJavaScript全てでdocument.headが事実上常時利用可能になるという便利な結果をもたらします。document.bodyは、あなたのscriptタグ内あるいはオープンタグのbodyの後に表示された場合のみ利用可能になります。

asyncdefer

HTML5には、スクリプトの実行を制御できるツールがいくつか追加されています。

  • asyncは、”いつでも実行する”を意味します。もっと具体的に言うと、「ページ全体がロードされ、全てのスクリプトが実行された後で実行されても構わない」ということになります。例えば、他にページが依存しているコードの実行がなかった場合など、アナリティクス収集する際のトラッキングコードを設定する時に役に立ちます。ページが必要とする変数や関数をasyncコードで定義することは残念ながらできません。なぜなら、いつasyncが実行されるか知ることができないからです。

  • deferは、”パースが終了するまで実行しない”を意味します。だいたいDOMContentLoadedイベントにスクリプトをバインドすることやjQuery.readyを使用することなどとほとんど変わりはありません。コードが実際に実行されるとき、DOM内の全てのものが利用可能になります。asyncとは異なりdeferコードはページのHTMLに表示された順番に実行されません。ただ、HTMLのパースが全て完了するまで延期(defer)されるのです。

type属性

これまで(Netscape 2以降)、scriptタグにtype="text/javascript"を特定してもしなくても、空白のままにしても問題はありませんでした。もし、typeとしてJavaScriptの派生以外のMIMEタイプを指定すると、ブラウザは実行しません。独自の言語を定義したい場合は問題ないでしょう。

<script type="text/emerald">
  make a social network
    but for cats
</script>

そうすると、次の例のように、コードの実行はあなた次第になります。

<script>
  var codez = document.querySelectorAll('script[type="text/emerald"]');
  for (var i=0; i < codez.length; i++)
    runEmeraldCode(codez[i].innerHTML);
</script>

runEmeraldCode関数の定義については読者への演習問題とします。

もし、どうしてもページの全てのscriptタグにデフォルト設定されたtypeを無効にしたい場合は、metaタグを使用することができます。

<meta http-equiv="Content-Script-Type" content="text/vbscript">

Content-Script-Typeヘッダを使用することもできます。

どのtypeが有効だったか、詳細はA Brief History of Weird Scripting Languages on the Web(Web上の奇妙なスクリプト言語の簡単な歴史)をお読みください。

integrity属性は存在するのか

integrity属性は新しいSubresource Integrity仕様の一部になります。これにより、スクリプトファイルが持つべきコンテンツに対してハッシュを与えることができます。つまり、送信されるscriptタグに極悪なコンテンツを挿入されるのを阻止する目的があるのです。SSLの世界では、code.jquery.comのように制御できない外部ソースからスクリプトをロードする際にとても貴重になります。

もし、これを使用する場合は、使用するハッシュやハッシュ値をハイフンで分けて含める必要があります。

<script
  src="//code.jquery.com/jquery.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC">
</script>

まだこれが実際に使用されているところを見たことがありません。もしご存知ならコメントで教えてください。

crossorigin属性も同様

crossorigin属性は、まだ完全には標準化されていませんが、サポートしているブラウザもあります。概念として、ブラウザは現在開いているページと異なる”オリジン(生成元)”からのリソースを制御します(オリジンとは、プロトコル、ホスト名、ポート番号を組み合わせた通信フォーマットです。例:http://google.com:80

これは、例えば「自分のWebサイトにアクセス中のユーザの、競合サイトのアカウントを削除してしまうようなリクエストを送信する」といったこと(最悪ですね)を防ぐために存在する機能です。しかし、scriptタグとのつながりは若干付随的です。ハンドラをwindow.onerrorイベントに適用する場合は、コードを外部サイトからロードする時に得られないページやスクリプトの情報を、ハンドラが持つことになります。セキュリティがしっかりしているブラウザでは、あなたがcrossoriginを指定しない限り、この情報は含まれません。

しかしcrossoriginは、魔法のセキュリティ・ハックではありません。何をする機能かと言えば、OPTIONSリクエストを行いAccess-Controlヘッダについてチェックするといった、通常のCORSアクセスチェックを行うようブラウザに指示することです。

document.currentScript

document.currentScriptは、珍しくIEで全くサポートされていませんが、現在実行されているスクリプト要素を返すプロパティです。これは、実行中の埋め込みコードのscriptタグから属性を抜き出したい時に、非常に役立ちます。個人的には、我が社Eagerで埋め込みコードをインストールする作業が少し大変になるので、この属性が完全にはサポートされていないことをうれしく思っています。

onafterscriptexecute

Firefoxでしかサポートされていないこの属性は、全く役に立ちません。onafterscriptexecuteonbeforescriptexecuteを使うと、そのページで実行される全てのスクリプトの実行前と実行後のイベントをバインドすることができるので、私はそれがすごいところだと思っています。

気になっている方もいると思うので補足ですが、イベントオブジェクトは実行するスクリプトを参照先として含み、beforeイベントは、preventDefault()を呼び出すことで実行をキャンセルできます。

for / event

今までHTML5の仕様ではまず見られなかった、イベントにコードをバインドするメソッドです。以前はIE固有のものでした。以下のコードで、ページのロードが終わるまでscriptタグの実行を止めておくことができるはずです。

<script for="window" event="onload">
  alert("Hi!")
</script>

私が試してみたところ、ChromeやFirefoxでは、このコードが動きませんでした。(つまり、標準に準拠しているわけではないということです)。しかし、IEでは動作する可能性が高いでしょう。

NOSCRIPT

あなたの両親にも若かった時代があるのと同じように、JavaScriptにも若かりし時代があったとは信じ難いことです。しかしながら、ブラウザがJavaScriptをサポートしているかどうかが分からなかった時代がありました。さらに悪いことには、ブラウザがscriptタグを認識するかどうかさえも分からなかったのです。そしてブラウザがタグを認識しない場合、ブラウザはそのタグの中身を一般的なインライン要素として表示していました。つまり、あなたが書いた秘密のJavaScriptがテキストとしてページに表示されてしまうということです。

幸運なことに、仕様的には十分な解決策が用意されており、コードをラップすることで、scriptタグをサポートしていないブラウザがHTMLコメントとしてコードを解釈することができます。

<script>
<!--  to hide script contents from old browsers

  // You would probably be pasting a ‘rollover’ script
  // you got from hotscripts.net here

// end hiding contents from old browsers  -->
</script>

御多分に漏れず、XHTMLは事態をさらに悪くしました。XMLはクロージングタグのようなものを含む可能性のあるコンテンツをエスケープするのに、かなり特別なメソッドを使います。そのためにCDATAが誕生したのです。

<script>
//<![CDATA[

    // Is this the right incantation to get this to pass
    // the XHTML validator?

//]]>
</script>

このように記述すれば、有効なXHTMLとなるでしょう。機能には何の影響も与えませんが、Web開発者としては、極めて重要なことです。

また、JavaScriptを処理できないブラウザを使う人々のために、ブラウザには有効なメソッドがあります。それはnoscriptタグです。<noscript>を使って、スクリプトの実行をブラウザがサポートしていない場合のみに表示されるコンテンツをラップしておきます。

<noscript>
  Please use Internet Explorer 5.5 or above.
</noscript>
<script>
  exploitInternetExplorer0Day();
</script>

観察力が鋭ければ、noscripttype引数を受け付けないことに気づくでしょう。やや曖昧ですが複数のtypesを使うページでは、ページとのやり取りを行います。実際の動きはブラウザによって異なりますが、ドキュメントの最初の方でサポートしていないtypeを使用しているscriptタグが使われている場合は、noscriptブロックを参照します。つまり、最初の方でnoscriptが書かれていなくても、最後の方で書かれている場合があるということです。

scriptタグとinnerHTML

DOMを介して動的に追加されたscriptタグは、ブラウザによって実行されます。

var myScript = document.createElement('script');
myScript.textContent = 'alert("✋")';
document.head.appendChild(myScript);

innerHTMLを介してページに動的に加えられたscriptタグは実行されません。

document.head.innerHTML += '<script>alert("✋")</script>';

分かりづらいですね。しかし、”Chromeインスペクタで、実行されていないscriptタグを認識するのは可能か?”のようなトリビアに答えるのは楽しいものです。さらに、このようなトリビアは、同僚をひっかけるいい方法にもなります。

お読みいただき、ありがとうございます。次の記事document.writeの変わった性質についてです。私たちの新しい記事がリリースされた時にお知らせがほしい方は、以下からお申し込みください。