JavaScript

JavaScript実行のタイミングと仕組み

ブラウザの機能

ブラウザはユーザのリクエスト(要求)をサーバに送信し、サーバが返すレスポンス(応答)を解析してWebページとして表示します。

ブラウザを構成する主な要素としてユーザ・インタフェース(UI)レイヤー、ネットワークレイヤー、レンダリングエンジン、JavaScriptエンジンがあります。この4つのコンポーネント(部品)の役割をみてみましょう。

ユーザ・インタフェース(UI)
ユーザ・インタフェース(UI)は、主にプログラムとユーザとの間を取り持ち、ユーザの指示をプログラムに伝え、プログラムから出力結果を返します。操作性、快適性といったユーザビリティに大きな影響を与えます。
コンポーネント(部品)説明
ユーザ・インタフェース(UI)レイヤーアドレスバーや戻る・進むボタン、メニューなどのインタフェースです。
ネットワークレイヤーサーバと通信してリソース(資源)をダウンロードし、レンダリングエンジンに渡します。
レンダリングエンジンHTMLやCSSの構文を解析し、DOM要素に変換、コンテンツを表示します。解析中にSCRIPTタグがあった場合、JavaScriptインタプリタに渡します。
JavaScriptエンジンJavaScriptを解析し、実行します。
DOM
DOM(Document Object Model)はHTMLドキュメントを操作するためのAPIです。DOMではHTMLドキュメントの構造をツリー状の集合として扱います。

ブラウザのコンポーネントはそれぞれ連携しながら、リソースの要求からレンダリングまでの処理を行っていきます。その処理の流れを少し詳しく追っていきましょう。
ブラウザはユーザからリクエストを受けると、HTTP/HTTPSプロトコルを使ってサーバにHTMLファイルや画像などのリソースを要求します。サーバ側は指定されたリソースを取得し、ブラウザに送信します。

ネットワークレイヤーはサーバから受け取ったリソースをレンダリングエンジンに渡します。レンダリングエンジンはHTMLドキュメントの上から順番に解析し、DOMノードで構成されたレンダーツリーを作成します。レンダーツリーを使ってレイアウト処理に進むと、各ノードに座標が割り当てられ、線画されます。

ここまでの処理は段階的に行われるのではなく、ある程度解析が済むとその分を線画し、さらに解析、線画が逐次的に繰り返されます。こうすることにより、白い何もない画面が表示される時間を最小にし、ユーザの体感速度を高めることができます。

次に、JavaScriptの解析と実行を追ってみましょう。レンダリングエンジンはHTMLの解析時にSCRIPTタグに到達すると、SRC属性がなければJavaScriptエンジンにSCRIPTタグ内のコードを渡します。SRC属性に値がある場合はネットワークレイヤにダウンロードの指示を渡し、読み終えたリソースはJavaScriptエンジンに渡されます。このとき、通常はリソースのダウンロードが終わりJavaScriptが解析されるまで、HTMLの解析は一時的に停止されます。

後述しますが、SCRIPTタグの属性指定でJavaScriptの解析とHTMLの解析を並行して進めさせる事ができます。

SCRIPTタグ

SCRIPTタグの属性

SCRIPTタグの属性のほとんどは外部スクリプトを読み込む際に使います。

SRC
外部スクリプトのURIを指定し、実行します。
ASYNC
この属性を指定すると、非同期でスクリプトを読み込み、実行します。非同期のため、ページのレンダリングは中断することなく進められます。
ASYNC指定のあるSCRIPTタグが複数ある場合、タグを書いた順番に関係なく読み込みが終了したものから実行されます。

 

ASYNCを指定する例

<script src="外部スクリプトのURL" async></script>
DEFER
この属性を指定すると、非同期でスクリプトを読み込みます。スクリプトの実行はHTMLの解析終了後です。
DEFER指定のあるSCRIPTタグが複数ある場合、タグを書いた順番に実行されます。

 

DEFERを指定する例

<script src="外部スクリプトのURL" defer></script>
CHARSET
CHARSET属性は、外部スクリプトファイルの文字コードを指定する際に使用します。
指定しなくてもほとんど問題ありませんが、呼び出し元のHTMLファイルと外部JavaScriptファイルで文字コードが違う場合にはエラーの原因となることがあります。その際は外部JavaScriptファイルの文字コード指定が必要になります。

 

CHARSETを指定する例

<script src="***" charset="UTF-8">

スクリプトの実行のタイミング

外部スクリプトファイルを読み込んだ場合、読み込まれたその行に記述されたのと同じように処理されます。
たとえば下記のように複数の外部スクリプトファイルを指定した場合、先に書いたfile1.jsが先に実行されることが保証されます。

<script src="file1.js"></script>
<script src="file2.js"></script>

これは外部スクリプトファイル、インライン(テキスト内)のスクリプトを問いません。

ブラウザが HTML の上から順番に解析していく際、SCRIPT タグで外部ファイルが指定されていれば、レンダリングを中断し、指定されたファイルを読み込みます。そのため、HEAD 要素内で外部スクリプトの指定があった場合、何もレンダリングされていない状態で処理がストップしてしまい、ユーザはしばらく白い画面を見ることになります。その上、JavaScript の実行はレンダリングより優先されるわけでもありません。
ユーザの体感速度を高めることを考えると、外部スクリプトファイルを読み込む SCRIPT タグは、HEAD 要素内ではなく、閉じ BODY タグの上に置いたほうが良いでしょう。

<html>
<body>
...
...
<script src="file2.js"></script>
</body>
</html>

JavaScript の読み込みは、SCRIPT タグの属性を使ってHTMLの解析と並行して進めることができます。たとえば、SCRIPT タグの ASYNC 属性や DEFER 属性を使った場合、外部スクリプトは非同期に読み込まれることになります。次の例では、ASYNC 属性が指定してあるため、HTML の解析と平行して console_log01.js と console_log02.js が読み込まれます。console_log01.js が先に記述されていますが、実行されるタイミングは先になるか後になるかはわかりません。console_log01.js が実行されると 01 、console_log02.js が実行されると 02 がコンソールに出力されます。
何度も再読込すると、console_log01.jsが先に実行されたり、console_log02.jsが先に実行されたりすることがわかると思います。

async.html
<html>
<body>
<script src="console_log01.js" async></script>
<script src="console_log02.js" async></script>
</body>
</html>
console_log01.js
console.log("01");
console_log02.js
console.log("02");

DEFER 属性を使った場合、非同期で読み込まれつつ、書いた順番通りに実行されます。defer.html を作成し、console_log01.js と console_log02.js を読み込むように SCRIPT タグを記述します。 async.html と違う点は、ASYNC属性の代わりに DEFER 属性を指定している点です。これで console_log01.js と console_log02.js は非同期で読み込まれ、記載した順番通りに実行されます。
defer.html を作成したら、ブラウザで読み込んでみてください。何度再読込しても、console_log01.jsが先に実行され出力結果は 01、02 の順番になります。

defer.html
<html>
<body>
<script src="console_log01.js" defer></script>
<script src="console_log02.js" defer></script>
</body>
</html>

非同期で読み込みつつ、順番関係なくなるべく早く実行したい場合は ASYNC 属性、順番通りに実行したい場合は DEFER 属性を使うと良いでしょう。

HTML5.0での簡略化

SCRIPTタグの開設に合わせて、HTML5.0で変更になったルールを紹介します。HTML5.0では様々な記述が簡素化されています。JavaSciptも例外ではなく、ここで紹介する書き方はHTML5.0で使うには不適切で、古いブラウザ向けとなっています。
今後使う機会は少ないと思いますが、まだHTML5.0に対応していないサイトがあるので、知識として知っておいた方が良いでしょう。

TYPE属性

HTML5以前ではTYPE属性が必須属性として定義されました。値には text/javascript を指定します。

<script type="text/javascript">
document.write("Welcome to Smart!!");
</script>

HTML5.0ではTYPE属性の初期値がtext/javascriptですので、JavaScriptの場合は属性の指定は不要になりました。

<script>
document.write("Welcome to Smart!!");
</script>

JavaScriptに対応していないブラウザ

JavaScriptに対応していないブラウザでJavaScriptのスクリプトファイルを読み込むと、SCRIPT要素の中身が画面に表示されてしまいます。
この問題を回避するために、スクリプト部分をHTMLコメント内に記述しておくことが出来ます。これで、JavaScriptに対応しているブラウザは通常通りスクリプトが実行され、JavaScriptに対応してないブラウザはコメントアウトとして処理します。

<script>
<!---コメントの始まり

document.write("Hello,JavaScript!!")

コメントの終わり //--->
</script>

Content-Script-Type

スクリプトの言語を指定するため、HTML5以前では、HTMLのヘッダ部に、下記のようなMETA記述を行うことを推奨していました。
HTML5.0では不要です。

<html>
<head>
<meta http-equiv="Content-Script-Type" content="text/javascript">
</head>
<body>
<input type="button" value="OK" onclick="~">
</body>
</html>

関連記事