読み込み完了後実行

このページに仕掛けられたjavascriptで、下表の「実行時刻」欄に実行時刻がミリ秒単位まで表示されます。

再読み込みすると時刻は変わります。X,A,B,C,Dがページ内リンクになっていて解説を見ることができます。Xは失敗していますが、javascriptの動作説明のために加えたもので未書込となっているのが正解です。

スクリプトを
置く場所
実行時刻 予測
順位
X head部に直書き 未書込 未書込
A 定番のwindow.onload 未書込 3
B body部table直後 未書込 1
C listner取り付け
(load)
未書込 3
D listner取り付け
(DOMContentLoaded)
未書込 2

端的に言うとjavascriptはページの読込中に動作を開始するので、ページの読み込み終了後でないと不都合なものはonloadで実行されるようにするということです。このページでは、それを実際の動作で説明しています。X,A,Bを比較してください。

このページを作る過程で、addEventListener()を使った方法が広まっていることを知って、これも含めてみました。それがC,Dです。

お勧めの方法

読み込み完了後実行する方法として、多くの場面でお勧めできるのは、古くから使われているこの書き方です。上の表ではAに相当します。

window.onload = function(){ 
   someFunction( argumentsIfNeeded ); //引数付き関数
}

ここでsomeFunctionは別の場所に書いてあるfunctionの名前、argumentsIfNeededは必要なら引数を書けるということです。

functionは複数書いてもいいし、functionにまとめずとも直接命令文を並べてもかまいません。

引数がないなら、

window.onload = function(){ 
   someFunction(); //引数なし
}

引数がない場合はさらに簡単に

window.onload = someFunction;

も可能です。

共通部分dispTime()の説明

A,B,C,D共通に使われるsomeFunctionの説明です。名前はdispTime()

引数whereで渡された文字列に合致したidをもつ要素を探して変数kokoにセットします。

koko!=nullつまり要素が見つかった時には、Dateオブジェクトを用意して、時分秒、ミリ秒を取得し連結してひとつの文字列とし、見つかった要素の最初の子ノードの値に代入して値を置き換えます。

<script>
function dispTime(where) {
   var koko = document.getElementById(where);
   if (koko!=null){
      var now = new Date();
      koko.firstChild.nodeValue = 
          now.getHours()+":"+
          now.getMinutes()+":"+
          (now.getSeconds() + now.getMilliseconds()/1000);
    }
}
</script>

idはtdタグに付いているので、firstChildはテキストノード。その値はすでにtdタグに入力されている文字列ということになります。初期値は「未書込」としてあります。

....
<tr><td id="idinhead">未書込</td>....
....

X:head部に直書き

head部のscriptタグ内に直に書いています。ここで直にというのはfunction{ }内に書かれていないという意味です。

<script>
function dispTime(where) {
   //同上
}
// 方法 X これは失敗します
dispTime("idinhead");
</script>

おっとfunctionが書いてありますが、これは前項で説明した共通部分です。X の独自部分はdispTime("idinhead");1行です。"idinhead"を引数にdispTime()を呼び出します。

htmlに書かれたjavascriptは基本、すぐに実行されます。function(){ }で書かれた部分は関数の定義で、すぐには実行されず、呼び出された時に実行されます。dispTime("idinhead");が実行される時には、当然dispTime()関数が定義されていないといけませんから、この順で並べています。

dispTime("idinhead");は実行されますが時刻を書くのは失敗します。それは id="idinhead"である要素がまだ読み込まれていないからです。表のX欄は未書込のままになります。

A:定番のwindow.onload

loadはデータをメモリ上に展開することです。window.onload はloadが終了したというイベントを表し、これに以下の関数の実行を結びつけます。この関数はここで定義されて、名前がない関数で{ }の内部がその内容です。

ここではdispTime()という関数を呼び出す文がひとつだけ入っていますが、{ }内には、普通の関数のように、いろいろ書き込むことは可能です。

<script>
function dispTime(where) {
   //同上
}
// 方法 A
window.onload = function(){ 
   dispTime("idonload");
} 
</script>

昔からある方法ですから、古いブラウザでも動作する、定番の方法です。

B:body部table直後に記入する

X:で日付が書けないのは、実行した時点で、「id="idinhead"である要素がまだ読み込まれていないから」と説明しました。では、id="idinhead" の要素の後にdispTime()を書けばいいんではないかという事になります。

これが、Bです。

<tr><td id="idinhead">未書込</td>....
<tr><td id="idonload">未書込</td>....
<tr><td id="idinbody">未書込</td>....
....
</tr>
</table>
<script>
dispTime("idinbody"); // 方法 B
</script>

これが実行されるのはページが全部読み込まれる前になりますから、onloadでの実行よりも前です。実行時刻が一番早い理由です。

スクリプトをbody部に置くことは正式に可能ですが、プログラムのメンテナンスという観点からは不利益の可能性があります。

一般的には、タグ部分、css部分、スクリプト部分を分けて書くのがよいとされています。もちろん事情にもよります。

C:listner取り付け(load)

addEventListener()というメソッド(関数みたいなものですが)を使ってload後に実行するものを設定する方法です。

window.onload 同様、どこに置いても良いので、head部に書くのが普通です。(もちろん別ファイルにして読み込むようにしてもOKです)。

window.onload を書く方法に比べて、addEventListenerを積極的に使う必要があるかは今のところ疑問です。

メリットとしては、すでにあるwindow.onloadの設定に影響なく動作を追加できるということがあります。(このページのA,Cがそれです)。window.onloadでは上書きされてしまい、もとのwindow.onloadが動作しなくなるという違いです。しかし、実行されるプログラムを把握できない状態で付け加えるのは危険ですから、このためにaddEventListenerを使うという事はまれでしょう。

loadイベント以外のイベントを統一的にこれで設定できるので、覚えることが減るというのはメリットかもしれません。loadイベント以外のイベントの設定については別の機会に。

// 方法 C
window.addEventListener('load', function() {
   dispTime("idaddevload");
});

"window.addEventListener('load'," が "window.onload =" に相当しています。

function以下は、window.onloadのときと同様、名前がない関数がここで定義されて、{ }の内部がその内容という形です。

別の見方をすると、この形はaddEventListener()関数が2つの引数を取っていると見ることもできます。一つ目はイベントの種類。2つ目は結びつける関数です。閉じかっこが最後に2つ重なって }) となっているのは、美しさにかけると思うのですが、Javaなどでも出てくる書き方です。

"idaddevload"はtdタグに付けられたidです。任意です。

D:listner取り付け(DOMContentLoaded)

イベントの種類が'load'から'DOMContentLoaded'に変わったものです。

// 方法 D
document.addEventListener('DOMContentLoaded', function() {
    dispTime("idaddDOMCon");
});

"idaddDOMCon"はtdタグに付けられたidです。任意です。

ターゲットはwindowでなくてdocumentです。windowのまま説明しているサイトもあり、それでも動作しますが、今のところdocumentの方が正しい書き方です。

C:の load はページの全ての要素がloadされた後というタイミングで発生するイベントで、window.onloadと同じです。実行時刻を見ても納得できます(同時刻かちょっと前だと思います)。load完了を待つ全ての要素には、画像や別ファイルのcss,scriptも含まれます。

対して、DOMContentLoaded はhtmlファイルのloadが終わった時点で発生するイベントです。

DOMはタグに基づくページの構造です。その構造がわかれば、ページ内のidやタグ名に基づいてページ内を書き換えることも可能になるというわけです。

DOMContentLoadedイベントが発生してなお、loadイベントがまだであるという状況を作りだそうとしてみましたが、今時大きな画像でも大して時間はかかりませんし、リンク切れなどで画像が表示されないときや、対象ファイルのURLでサーバーが見つからない時もすぐに諦めてloadイベントを発生させてしまい、なかなかDOMContentLoadedのありがたみを味わえるページを作れませんでした。

でも、最近広告の多いウェブページで、広告部分の表示に時間がかかるのをよく見かけます。こういうページをiframeで取り込んで表示してみると、loadイベントはなかなか発生しませんでした。こんなページの場合にDOM内の作業、たとえば本文のボタンにでonclickを取り付ける作業などをさせるのなら、DOMContentLoadedは有効です。

DOMContentLoadedについて

このページを書くにあたって、addEventListener()について、特にDOMContentLoadedについて調べることになりました。

インターネットエクスプローラ(IE)は対応が遅くver.9から使えるという情報はすぐにたくさん見つかりましたが、ターゲットがwindowなのかdocumentなのかを仕様書で確かめようとしてもなかなかたどり着けませんでした。

javascriptは Ecma International が定める ECMAScript の仕様がベースですが、htmlドキュメントを操作するにはW3Cの定めるDOMとイベントの仕様に合わせる必要があります。その上でGoogle、Mozilla、Microsoftの実装がそれぞれ少しずつ異なって存在します。

DOMのイベントの項には load, unload, abort, error, select, change, submit, reset, resize, scroll と並んでいますが、DOMContentLoadedがありません。html5で定義されていることが判明しました。

相互に絡み合っている部分ではありますが、統一してもらうか、境界を決めて住み分けしてほしいものです。

なかなか魅力的な機能ではありますが、loadに時間がかかるページをつくることはあまりないこと、読み込んだ画像に何かをする場合には支障が出るかもしれないこと、古いブラウザがもう少し残っているかもしれないことなどから、もう少し控えておいたほうが良さそうです。

このページのスクリプト全体

body中に置いたものは省いています。

<script>
function dispTime(where) {
   var koko = document.getElementById(where);
   if (koko!=null){
      var now = new Date();
      koko.firstChild.nodeValue = 
          now.getHours()+":"+
          now.getMinutes()+":"+
          (now.getSeconds() + now.getMilliseconds()/1000);
    }
}
// 方法 X これは失敗します
dispTime("idinhead");
// 方法 C
window.addEventListener('load', function() {
   dispTime("idaddevload");
});
// 方法 D
document.addEventListener('DOMContentLoaded', function() {
    dispTime("idaddDOMCon");
});
// 方法 A
window.onload = function(){ 
   dispTime("idonload");
} 
</script>