POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

FeedlyRSSTwitterFacebook
Zach Bloom

本記事は、原著者の許諾のもとに翻訳・掲載しております。

もうすでにご存じだとは思いますが、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によって利用可能になります( querySelectorAll jQuery などを通して)。

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

async defer

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

気になっている方もいると思うので補足ですが、イベントオブジェクトは実行するスクリプトを参照先として含み、 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>

観察力が鋭ければ、 noscript type 引数を受け付けないことに気づくでしょう。やや曖昧ですが複数の 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 の変わった性質についてです。私たちの新しい記事がリリースされた時にお知らせがほしい方は、以下からお申し込みください。

監修者
監修者_古川陽介
古川陽介
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
複合機メーカー、ゲーム会社を経て、2016年に株式会社リクルートテクノロジーズ(現リクルート)入社。 現在はAPソリューショングループのマネジャーとしてアプリ基盤の改善や運用、各種開発支援ツールの開発、またテックリードとしてエンジニアチームの支援や育成までを担う。 2019年より株式会社ニジボックスを兼務し、室長としてエンジニア育成基盤の設計、技術指南も遂行。 Node.js 日本ユーザーグループの代表を務め、Node学園祭などを主宰。