DOMの準備完了を待つ
HTMLに含まれるDOM要素の構築が完了しないうちにJavaScriptでDOM要素を参照しようとするとエラーが発生します。これは比較的よくあるミスで、解決方法は至って簡単です。
実際の例を見てみましょう。リスト5のHTMLをブラウザで読み込むと、変数mainDivはnullとなります。これは、<script>内のJavaScriptが実行される時点ではまだdiv要素の構築が完了していないためです。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script>
var mainDiv = document.getElementById('main');
console.log(mainDiv); // mainDivはnullとなる
</script>
</head>
<body>
<div id="main"></div>
</body>
</html>
古くから存在する対処法としては、onloadイベント後にJavaScriptが実行されるようにすることでエラーを回避できます。window.onloadに関数を代入すると、ページのDOM構築が完了し、画像やJavaScriptなどの外部ファイルのロードがすべて完了した段階でその関数が実行されます。onloadイベントが発生した時点であればdocument.getElementById()でページ内のすべての要素を取得できます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script>
// 既存の変数を上書きしないようにスコープを作成して実行する
(function(){
// オリジナルのonloadを保存しておく
var _onload = window.onload;
window.onload = function() {
// オリジナルのonloadが存在する場合は実行する
if (typeof _onload === 'function') {
_onload.apply(this, arguments);
}
var mainDiv = document.getElementById('main');
console.log(mainDiv);
};
})();
</script>
</head>
<body>
<div id="main"></div>
</body>
</html>
しかし、その後jQueryが多くの開発者に使われるようになり、jQueryのready()メソッドを使う方法が一般的になりました。window.onloadは画像を含めたすべてのロードの完了後に実行されるのに対して、jQueryのready()はDOM readyイベントと呼ばれ、DOMの構築が完了した時点で関数が実行されます。つまり、画像などのリソースが大量に埋め込まれているページでも、画像の読み込み完了を待たずにDOM readyイベントで関数を実行することができます。DOM readyイベントで実行される関数を登録するには$(document).ready(関数)を実行します(リスト7)。他の関数がすでに登録されていても気にすることなく追加登録できます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
$(document).ready(function() {
var mainDiv = document.getElementById('main');
console.log(mainDiv);
});
</script>
</head>
<body>
<div id="main"></div>
</body>
</html>
$(document).ready(関数)の代わりに$(関数)と短く書くこともできます(リスト8)。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
$(function() {
var mainDiv = document.getElementById('main');
console.log(mainDiv);
});
</script>
</head>
<body>
<div id="main"></div>
</body>
</html>
他の手法としては、<script>を</body>の直前に置くことで、実行中の<script>より前の部分のDOM構築が完了していることを前提に処理を進めることができます(リスト9)。ただしdocument.write()を使っているJavaScriptコードの実行箇所を移動すると問題が生じるため、そのようなJavaScriptにはこの方法は使えません。jQueryのready()は汎用的に使うことができ、ライブラリとして公開する場合など実際にどのタイミングで読み込まれるかわからない場合などに便利です。これら2つの手法を組み合わせてready()を</body>の直前に置いても問題ありません。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="main"></div>
<script>
var mainDiv = document.getElementById('main');
console.log(mainDiv);
</script>
</body>
</html>
JavaScriptとCSSの最適な読み込み順序
一般的に、CSSは<head>内で読み込み、JavaScriptは<body>の最後、つまり</body>の直前で読み込むのがよいとされています。描画が始まってからCSSを読み込むと、CSSが適用されていないページが一瞬表示されてしまいます。また<head>内や<body>の途中で外部JavaScriptを読み込むとそのロードが完了するまでそれ以降の描画は行われません。CSSを読み込むタグは<head>内にまとめておき、またJavaScriptを読み込むタグはbodyの描画前に実行しなければならないものやdocument.write()を使用しているもの以外は</body>の直前に置くとよいでしょう。
jQueryなど必ずしもページ描画前にロードする必要のないライブラリを</body>の直前で読み込むようにするだけでユーザーから見た体感速度が向上することもあります。
スクリプトローダーで高速に読み込む
JavaScriptの読み込みを高速化する手段としてスクリプトローダーを使うこともできます。スクリプトローダーとは、JavaScriptをロードするためのJavaScriptライブラリです。
高速なスクリプトローダーの一つにLABjs(MITライセンス)があります。LABjsを使うと、スクリプトの読み込みが様々なテクニックで高速化されます。LABjsで外部JavaScriptをロードする処理をすべて<head>内に入れておいても、ページの描画を中断させることなくスクリプトのロードを同時並行で行うことができます。
LABjsの使い方
LABjsのWebサイトからファイルをダウンロードし、LAB.min.jsを自分のサイトに設置します。
LABjsは通常、リスト10のようにHTMLに埋め込んで使います。このブロックは</body>の直前に入れても構いませんし、<head>内に入れても構いません。CoffeeScriptで外部スクリプトとして書くこともできますが、HTMLに埋め込んだ方が読み込みが高速になります。あるいはLAB.min.jsと自分のJavaScriptを結合させて外部ファイルにして読み込むのもよいでしょう。
<script src="js/LAB.src.js"></script>
<script>
$LAB
.script('https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js')
.script('js/underscore-min.js').wait()
.script('js/myscript.js');
</script>
外部JavaScriptを読み込むには$LAB.script()を呼びます。
$LAB.script(URL);
LABjsのメソッドはドットを続けて書いていくことができます。このような鎖状の書き方を一般的にメソッドチェーンと呼びます。
$LAB.script('script1.js').script('script2.js').script('script3.js');
$LAB.wait()をチェーンの中に置くと、それ以前のスクリプトの実行完了を待ってから次のスクリプトを実行します。以下の例では、script1.jsとscript2.jsの実行が完了してからscript3.jsが実行されます。
$LAB
.script('script1.js')
.script('script2.js').wait()
.script('script3.js');
例えばjQueryに依存しているライブラリはjQueryより後に実行する必要があるため、jQueryの後に$LAB.wait()を挟みます。$LAB.wait()を入れてもスクリプトのダウンロード自体は同時並行で行われ実行順序だけが制御されるので、時間の無駄は最小限に抑えることができます。
また$LAB.wait()には引数として関数を渡すことができ、それ以前のスクリプトの実行が完了した時点で任意の関数を実行できます。
$LAB
.script('script1.js')
.script('script2.js')
.wait(function() {
console.log("script1と2の実行が完了");
})
.script('script3.js');
LABjsはIE6以上など現在使われているほぼ全てのブラウザで動作します。
通常は</body>の直前に<script>を置くだけでも十分ですが、特にスピードが重視される場面ではLABjsを使うとさらに高速化できる場合があります。ただし、LABjsの読み込みや実行にもわずかながらオーバーヘッドが発生するため、どちらが高速か実験してから使うとよいでしょう。
