2007年11月16日金曜日

DOM のロード直後に実行する関数の指定方法

Javascript で DOM を操作する際にはページ内の DOM 要素がロードされている必要がある。
このとき問題になるのは、
  • DOM がロードされたタイミングをどのように検知するか。
  • DOM がロードされたタイミングで実行する関数の指定方法。
ということになる。

まず、次のような html を考える。
<html>
<head>
<title></title>
<script>
// ページヘッダの読み込み時刻を記録
var start = new Date();
</script>
</head>
<body>

<!--ロードに10秒かかる画像を指定-->
<img src="http://example.com/sleep10" />
</body>
</html>

ここで、 image 要素には 10 秒後に結果を返すスクリプトを指定している。
window.onload を使い、ページのヘッダが読み込まれてから、関数が実行されるまでの実行時間を計測し、Firebug コンソールに書き込む。
window.onload = function(){
// 関数が実行され始める時刻を記録
stop = new Date();
// firebug のコンソールにログ出力
console.log('window.onload' + (stop - start));
}

/////window.onload 100005

window.onload で指定した関数はページ内の画像ファイルが全てロードされてから実行されるので、結果は、以下のようになる。
画像のロード時間 (10秒) + 関数の実行時間

しかし、DOM を操作するために、画像が完全にロードされている必要はない。
そこで、以下のように、body の最後の子要素に、実行したい関数を書き、DOM がロードされたら自動的に関数が実行されるようにする。
この方法だと、DOM がロードされているが、画像のロードを待たずにすむので、先程より速くなる。
<html>
<head>
<title></title>
<script>
var start = new Date();
</script>
</head>
<body>

<!--ロードに10秒かかる画像を指定-->
<img src="http://example.com/sleep10" />

<!--この位置では全ての DOM 要素は読み込まれている-->
<script>
stop = new Date();
console.log('script-end ' + (stop - start));
</script>
</body>
</html>

/////script-end 5
ただし、この方法は html 内に余計なDOM 要素を加えるので一般的に推奨されていない。
jQuery では、以下のように function 部分に任意の関数を指定すると、
$(document).ready(function(){});
DOM 要素がロードされてから、実行される関数を指定可能である。
ロード後に実行したい関数を指定できる数に制限はなく、DOM ロード後に実行したい関数が複数ある場合は、
// DOM ロード後に実行したい 1 つ目の関数
$(document).ready(function1(););

// DOM ロード後に実行したい 2 つ目の関数
$(document).ready(function2(););
....
という書き方ができる。
これと同様に、jQuery を使わない場合でも、以下のように DOM ロード直後に実行する関数を指定できると嬉しい。
domReady(function(){
stop = new Date();
console.log('using domReady ' + (stop - start));
});

////using domReady 5
この原理は、以下の 2 つの関数で達成される。
function domReady(f){
// DOM がロードされているときは関数をそのまま実行
if(domReady.done) return f();

// ロード後に実行する関数が複数ある場合は配列に挿入
if(domReady.timer){
domReady.ready.push(f);
}
else {
// DOM がロードされる方がisDomReady で
// 検知される場合より早かった場合の対策。
window.onload = function(){isDomReady();};

// DOM ロード直後に実行する関数の配列の初期化
domReady.ready = [f];

// DOM がロードされているかを定期的にチェック
domReady.timer = setInterval(isDomReady, 13);
}
}

/**
* DOM がロードされているかをチェックする。
*/
function isDomReady(){
if (domReady.done) return false;

// ページ内の DOM 要素がロードされているかをチェックしている。
if (document && document.getElementsByTagName &&
document.getElementById && document.body) {

clearInterval(domReady.timer);
domReady.timer = null;

// 予約された関数を順次実行
for(var i = 0, l = domReady.ready.length; i < l; i++) {
domReady.ready[i]();
}

domReady.ready = null;
domReady.done = true;
}
}

jQuery などのライブラリを使わない場合で、DOM ロード直後に実行する関数を指定したい場合に使えます。