JavaScriptの技法:DOM操作の落とし穴とその回避方法

文:Nick Gibson(Builder AU)
翻訳校正:原井彰弘
2008/01/11 12:00

JavaScriptでDOMを操作するのはAjaxアプリケーションでよく使われる。しかし、JavaScriptによるDOM探索は手間がかかり、落とし穴もある。ここではその回避方法を探ってみよう。

 JavaScriptは、ウェブアプリケーションの開発で現在もっとも使用されている非常に便利なツールだ。しかし、JavaScriptはブラウザ内でリアルタイムに解釈されて動作するため、Document Object Model(DOM)を扱う際には細心の注意が必要となる。

 DOMは、ウェブページを記録するためにブラウザで用いられているデータ構造だ。DOMはタグで構成されるツリー構造をしており、それぞれのタグにはHTML(またはXML)ドキュメントの形式に対応した複数のタグが子ノードとして含まれている。これらの内容は、JavaScriptを利用すればページが読み込まれた後でも動的に調べて変更することが可能であり、これがAJAXの大きな基盤となっている。

 しかしながら、JavaScriptを用いてDOMの内部を探索するのは手間のかかる作業である。なぜなら、JavaScriptが保持しているすべての参照は動的であり、作業中に内容が変更される可能性もあるからだ。そのため、思いつきのアルゴリズムは多くの場合正しく動作しない。もし、スコープを変更したときに余分なメモリを割り当てて、変数をキャッシュするようなプログラミング言語を使用しており、その振る舞いに慣れてしまっているなら、なおさらそうである。

 短い例を用いてこの問題を分かりやすく説明しよう。ここではJavaScriptを用い、DOMツリーを表現したテキストでページの内容を置換する関数を記述する。さて、ページの内部がどうなるのかを見て欲しい。例としては、以下のようなページを用いる。

<html>
	<head><title>Test Title</title></head>
	<body>
		<p>Test Text</p>
		<p><a href="http://builderau.com.au/">BuilderAU</a></p>
	</body>
</html>

 まず、ファイルの先頭に<script>要素を追加することから始めよう。この要素にはDOMの内容を出力するはずのprintDOM関数が含まれている。以下のようにしてbodyタグにコードを追加し、読み込み時にdocumentに対してprintDOM関数を実行するよう設定する。

<body onload="printDOM(document)">

 続いてprintDOM関数を実装するのだが、まず以下のような実装を試みよう。

function printDOM(x) {
	document.write(x)
	for (var i = 0; i < x.children.length; i++) {
		printDOM(x.children[i]);
	}
}

 ところで、このコードは以下のような出力を行う。

[object HTMLDocument][object HTMLHtmlElement][object HTMLHeadElement][object HTMLBodyElement][object Text]

 一見このコードは正しく動作しているように見える。しかし、ドキュメントのもっとも重要な部分を忘れてはいけない。<p>タグとリンクがまったく含まれていないのだ。この原因は単純である。DOMをたどると同時にDOMに出力を行っているので、そのときに内容が変更されるのである。従って、HTMLの要素の子ノードを調べようとするときには、そのノードはすでに変更されて出力中のデータになっているのだ。ここでの教訓は、ドキュメントへの出力とドキュメントの過去の内容を調べる行為は、同時に行ってはいけないということである。

記事の感想やご意見をコメントでお寄せください(CNET_IDログインが必要です)
ログイン パスワードを忘れた方  |  新規登録
  • 新着記事
  • 人気記事
  • 特集
  • ブログ