オブジェクトがありません【エラー】


0   名前: hiyokko : 2008/03/26(水) 10:35  ID:4dCTJkod sub-i.
●「リストの最初にあるテキスト"あいうえお"を取得する」というサンプルです。
以下のようにコードを書くと「オブジェクトがありません」とエラーが出ます。

/*----------------------------
エラーが出るソース
------------------------------*/

<script type="text/javascript">
<!--
var Obj = document.getElementById("list").firstChild.firstChild.nodeValue;
alert(Obj);
// -->
</script>

<ul id="list">
<li>あいうえお</li>
<li>かきくけこ</li>
<li>さしすせそ</li>
</ul>
------------------------------------

先ほどのソースのスクリプト部分に関数を加えるとエラーが出なくなります。

onload = function() {
var Obj = document.getElementById("list").firstChild.firstChild.nodeValue;
alert(Obj);
}

なぜ関数が無いとエラーが出るのでしょうか。どなたか教えて下さい。

1   名前: 1 : 2008/03/26(水) 10:35  ID:TwDxXnkP sub-rt
HTML 文書は冒頭から順に解析され、script 要素が見つかった時点でスクリプトエンジンに渡される。従って、script 要素が出現した時点では、まだ ul#list 要素は出現しておらず、Obj は null となる。

script 要素に defer 属性を付ければ、そのスクリプトコードは HTML 文書の読み込みが終わった時点で実行されるので、ul#list 要素も取得できる。しかし、この属性に対応しているのは現状 IE のみ。

ルートノード(この場合 document)で DOMNodeInserted イベントを監視すれば、ul#list 要素が出現した時点でコードを実行できる。だが、IE、Opera はこのイベントに対応していない。

非標準だが、DOMContentLoaded イベントを監視すれば、defer 属性と同様に、HTML 文書を読み込み終えた時点でコードを実行できる。だが、IE が対応していない。

同じく非標準だが、document.readyState を見れば、ページの読み込み中か、読み込み終えたか、関連リソースも含め全て準備完了したか、などが分かる。タイマを使って監視すれば、適当なタイミングでコードを実行できる。だが、これに対応しているのは IE、Safari のみで、かつ IE の値がビミョーにおかしい。

仕方がないので、load イベントを使って、HTML 文書および関連リソースを全て読み込み終わるのを待ってからコードを実行するやり方が、現状では多い。それが onload ハンドラの意味だ。もっとも、他のやり方が選択可能なら、そっちを選んだ方が良い。個人的にあまりおススメはしない。おそらく script 要素のベストな位置は、文書の末尾だ。



> 「リストの最初にあるテキスト"あいうえお"を取得する」というサンプル

レガシー HTML で、かつ IE ならば、そうなるかもしれない。だが間違いだ。<ul> と <li> の間にテキストノード(改行)がある。
var node = document.getElementById('list').childNodes[1].childNodes[0].data;

もちろん、これは IE でうまくいかない。では、違いを吸収するにはどうすれば良いか? 考えてみてくれ。

ちなみに、IE でも IXMLDOMDocument.preserveWhiteSpace を設定することで、改行だけのテキストノードも保持することができる。また、DOM3-Core で Document.domConfig.setParameter ('element-content-whitespace', false) すれば、IE と同じく改行だけのテキストノードを捨てることになっているが、現状でこれを実装しているブラウザは存在しない。

実際問題として、Range を扱うのでなければ、改行だけのテキストノードを保持する理由はあまりない。そこで現在、
var obj = document.getElementById('list').firstElementChild.firstChild.data;

と書けるようにする案が検討されている(W3C: ElementTraversal)。Opera、Safari あたりがそのうち実装するだろう。Element.prototype を拡張できる実装なら、先んじて作っておいても悪くない。



もう一つ、気になったのだが、

> /*----------------------------

もし script 要素内をコメントアウトするのなら、こう書いてはいけない。HTML のコメントとして不正だ。/*__________、/*========== とでもした方が良い。

2   名前: 2 : 2008/03/26(水) 10:35  ID:TwDxXnkP sub-rt
>>1
> おそらく script 要素のベストな位置は、文書の末尾だ。

補足だが、このことは特に、外部ファイルのときに考慮する必要がある。
<script type="application/ecmascript" src="http://example.com/hoge.js"></script>
....

defer 属性がない限り、ファイル取得&実行が完了するまでレンダリングが止まる。ここで、もしサーバの調子が悪かったり、回線状態が悪いと、いつまでもファイルが届かない。その結果、(HTML 内容は全て届いているにも関わらず!)白紙のままになる。こういう状況は、意外に頻繁に生じているのだが、対処されている様子はあまり見たことがない。

3   名前: hiyokko : 2008/03/26(水) 10:35  ID:4dCTJkod sub-i.
なるほど。わかりやすい回答ありがとうございます。

【エラーの原因】
・HTMLは上から順に読み込まれる
・<ul>などの要素の読み込み前にスクリプトが実行された場合、スクリプトは<ul>などの要素を認識できない。 → 「オブジェクトがありません」とエラーが発生する。

【対処法】
・スクリプトをソースの後ろの方に配置する
・onloadなどのイベントを使ってHTMLの読み込みが終わってから実行する
・<script type="xx" defer="defer">のように書いて、スクリプトを後回しにする。
-------------------------------------------------------------------------

>レガシー HTML で、かつ IE ならば、そうなるかもしれない。だが間違いだ。
改行のことをすっかり忘れていました…。FirefoxとIEの改行の扱いの違いは間違えやすいので気をつけなければなりませんね。

あと、nodeValueを使わなくても、dataを使えばテキストを取り出せるんですね。
これからのスクリプト作成に大いに活用させていただきます。回答ありがとうございました。

一覧へ戻る