DOM (Document Object Model) は W3C (World Wide Web Consortium) により、異なる仕様グループ (DOM Level 1、DOM Level 2、および DOM Level 3) で定義されています。DOM は、HTML または XML 文書を、プロパティーとメソッドを持つノードの階層からなるツリーとして表現します。JavaScript などのクライアント・サイドの言語を使用してツリー内のノードに対する追加、変更、削除、そしてイベントの関連付けを行うことで、インタラクティブで動的な Web ページを生成することが可能になります。
クライアント・サイド・スクリプト言語 (JavaScript) で DOM を変更することを、DOM スクリプティングと呼びます。Web 開発では、HTML、CSS、および JavaScript によってインタラクティブな Web ページを作成することを DHTML (Dynamic HTML) と総称してきましたが、DOM スクリプティングという言葉は、この DHTML の代わりに使用されます。
この記事では、DOM API で最もよく使われているメソッドと属性について詳しく探ります。詳細な例を用いて JavaScript で DOM をトラバースする方法を説明し、さらに複雑なモデルでは、どのような場合にイベントとリスナーが考慮されるかを説明します。また、JavaScript ライブラリーを利用して DOM を操作する方法を学んでください。
記事で使用するソース・コードは、ダウンロードすることができます。また、記事で取り上げる概念についてさらに深く調べられるように、「参考文献」にリンクを記載してあります。
DOM の用語では、文書は「ツリーのルート」として表され、JavaScript でこれに該当するのは、window.document です。あるいは単に document と表現されることもあります (文書は Window
オブジェクトに関連付けられるためです)。一部の JavaScript 実装では、このオブジェクトが出発点となります。リスト 1 に、HTML フラグメントの一例を記載します。
リスト 1. HTML コード
<body>
<p id="paragraph1">
<span>This is some text</span>
<a href="/index.html" title="Click here">Click here</a>
<p>
</body>
|
DOM の見方では、上記の例で使用されている p タグは DOM の Element インターフェースで表現されます。これは、span タグおよび
a タグの親です。span タグと a タグは兄弟の関係にあります。
例えば、リスト 1 のコードで使われているアンカーの href 属性を取得するとします。DOM
内の要素には、getElementById メソッドを使用すれば簡単にアクセスすることができます。Element getElementById (in DOMString elementId) というコードは、IDL (Interface
Definition Language) で記述した getElementById シグニチャーを含む document インターフェースの定義部分を表しています。
JavaScript は String オブジェクトを使って DOMString インターフェースを実装するため、このメソッドは、ストリング形式の要素 ID
をパラメーターとして受け入れます。上記の例で p タグには唯一 id 属性があるので、var paragraph =
document.getElementById("paragraph1"); を使用すれば、この要素を取得することができます。
p タグ内にネストされたアンカーを取得するには、childNodes 属性を使用します。この属性は Node インターフェースに属し、NodeList 型のオブジェクトを返します。このオブジェクトは、JavaScript
のオブジェクトのなかでも配列に似たオブジェクトで、pop() や push() などのメソッドはありませんが、length
プロパティーがあります。childNodes 属性から返されるオブジェクトは、ノード要素 (HTML
タグ)、テキスト・ノード、またはコメント・ノードを区別しません。単にノード要素を探しているというのであれば、children 属性を使用するという方法もあります。この属性を使えば、テキスト・ノードやコメント・ノードのことが考慮されないため、childNodes よりも素早く要素を取得することができます。この例では、アンカーはパラグラフの 2 番目の子となっているので、var aElement = paragraph.children[1]; を使用して取得することができます。
ある要素の href 属性の値を取得するには、getAttribute メソッドを適用して、属性の名前 (この例では href)
をパラメーターとして渡します。IDL 定義での getAttribute メソッドが含まれる部分は、DOMString
getAttribute (in DOMString name) です。
この例では、上記のインターフェースを var aHref = aElement.getAttribute("href"); //
"index.html" として実装することができます。
JavaScript での場合と同じく、複数のメソッドをつなげることもできます。a タグの href 属性の値を 1 行のコードで取得するには、var aHref =
document.getElementById("paragraph1").children[1].getAttribute("href"); // index.html */ を使用してください。
DOM スクリプティングの詳細: サンプル・アプリケーション
このセクションでは、DOM スクリプティングの機能をいくつか取り上げて詳しく説明します。サンプルとして使用する Sticky Notes アプリケーションは、ユーザーがページをリロードせずに付箋メモを追加することができるインタラクティブな Web ページで、図 1 のように表示されます。
図 1. Sticky Notes アプリケーションのフロントエンド
リスト 2 に、図 1 に示したページの HTML コードを記載します。head タグ内に指定されているのは、CSS ファイルと JS ファイルへの参照です。body タグの内容を見るとわかるように、ページに表示されているメモは、textarea タグと、新規メモの作成をトリガーするアンカーで構成されています。
リスト 2. HTML コード
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8">
<title>
Dom Scripting
</title>
<link rel="stylesheet" href="css/master.css" />
<script src="js/script.js"></script>
</head>
<body>
<div class="wrapper">
<h1> Sticky Notes </h1>
<div class="links">
<textarea id="contentArea" cols="10"> </textarea>
<a href="/random.html" class="add">Click here</a>
<span>to add a sticky note</span>
</div>
<div id="notes">
<div class="note">
<p>
This is a note
</p>
</div>
</div>
</div>
</body>
</html>
|
このページがロードする script.js ファイル内の JavaScript
コードについて、分析していきましょう。スクリプトのロジックは、ページのロードが完了した時点、または文書が作成された時点でトリガーする必要があります。そのための 1
つの方法は、関数を onload ウィンドウ属性にバインドすることです (リスト
3 を参照)。
リスト 3.
onload 属性
window.onload = init;
function init() {}
|
onload 属性は、DOM のロード・イベントに関連付けられます。DOM Level 0
でイベントをリスナー関数にバインドするには、これが通常の方法です。DOM Level 0
は、すべてのブラウザーでサポートされる「仕様」ですが、標準ではありません。その一方、DOM Level 2 仕様には標準 DOM
イベント・モデルが定義されています。この仕様では、イベント・ハンドラーをターゲット要素に登録するためのメソッドとして、(EventTarget インターフェースの) addEventListener
を定義しています。このメソッドのシグニチャーは、object.addEventListener(eventType,
eventHandler, useCapture); です。
eventType はオブジェクトに登録するイベントで、eventHandler は特定のイベントにバインドする関数です。useCapture はオプションのブール値で、イベント・フローのどのフェーズ (キャプチャー・フェーズまたはバブリング・フェーズ)
で関数を呼び出すかを定義します。addEventListener 関数を使用して load
メソッドをウィンドウにバインドするためのコードは、window.addEventListener("load", init,
false); です。
残念ながら、バージョン 9 より前の Internet Explorer (IE) は上述の W3C メソッドをサポートしていません。代わりに、独自の実装として object.attachListener(eventType, eventHandler); を使用します。IE がサポートする DOM
Level 3 イベントについては、「参考文献」を参照してください。
eventType では、イベント名に接頭辞 on
を付ける必要があります。IE のイベントはデフォルトでバブリングを行うため、useCapture パラメーターはありません。
リスト 4 に、script.js から抜粋した addEvent
関数を記載します。すべてのブラウザーでイベント・バインディングを処理するこの関数は、SA
という名前のグローバル・オブジェクトのメソッドです。このメソッドは、これまでに説明したすべての手法で使用することができます。
リスト 4.
addEvent 関数
window.SA = {
addEvent : function(element, evType, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener (evType, fn, useCapture);
return true;
} else if (element.attachEvent) {
var r = element.attachEvent('on' + evType, fn);
return r;
} else {
element['on' + evType] = fn;
}
}
}
|
addEvent 関数を使用する場合には、load イベントに関数 (ここでは SA.load という関数にします) をバインドすることができます (リスト 5 を参照)。
リスト 5. 関数をバインドする
SA.addEvent(window, "load", SA.load, false);
SA = {
...
load : function() {
// init block
}
}
|
上記の SA.load 関数は、load
イベントに関連付けられていることから、すべてのリソースがダウンロードされた場合にのみ呼び出されます。一般的なシナリオでは、load
イベントに関連付けられた関数は実行されるまでに多少時間がかかります。これは、ページに多数の画像がダウンロードされる場合には尚更のことです。そこで適切なプラクティスとなるのが、スクリプトを初期化する関数を
DOMContentLoaded イベントに関連付けることです。最近のブラウザーでサポートされているこのイベントは、DOM が作成されるとトリガーされます。スクリプトを初期化する関数は、外部リソースがダウンロードされる前に実行されるため、ページの応答性が良くなります。バージョン 9 より前の IE には、すぐに使用できる DOMContentLoaded イベントが組み込まれていないため、他のブラウザーと同じような動作をさせるには何らかの手段が必要です。ただし、この例ではページに画像がないので、このロード手法をそのまま使用することができます (ページのパフォーマンスが大幅に影響されることはありません)。
これで、関数ハンドラーをターゲット・アンカーの click イベントに関連付ける用意ができました。そこで、ユーザーがアンカーをクリックすると、特定の振る舞いが実行されるようにします。この例でその振る舞いに該当するのは、新しいメモの作成です。そのための最初のタスクは、DOM をトラバースして、ターゲットとしているアンカーを取得します (リスト 6 を参照)
リスト 6.
add をクラス名に持つアンカーを取得する
load : function() {
var anchorSelected;
if (document.getElementsByClassName) {
anchorSelected = document.getElementsByClassName("add")[0];
} else {
var anchors = document.getElementsByTagName("a"),
alenght = anchors.length;
for (var i = 0; i < alenght; i++ ) {
var anchor = anchors[i];
if (anchor.className === "add") {
anchorSelected = anchor;
}
}
}
}
|
リスト 6 の document.getElementsByClassName
メソッドは、おそらく予想できると思いますが、クラス名を指定して要素を取得するために使用します。このメソッドは一連の HTML
要素を返しますが、残念ながらすべてのブラウザーがこのメソッドを完全にサポートしているわけではありません。それに該当するのは、例えば、IE6 と IE7
です。このようなブラウザーには、別のロジックを作成しなければなりません。別のロジックとしては、最初に document.getElementsByTagName
メソッドによってアンカーのリストを取得し、そのリストをループ処理することで、add という CSS
クラス名のアンカーを取得することができます。getElementsByTagName メソッドが正式に返すのは
NodeList オブジェクトです。このオブジェクトは幸い、すべての主要なブラウザーで完全にサポートされています。
リスト 6 には、アンカーの配列のサイズを alength 変数に格納する方法が示されています。こうすれば、for-loop で DOM に対して一度問い合わせをするだけで済みます。DOM を変更した上で操作するのはコストのかかる処理なので、DOM を操作する回数はできるだけ少なくしてください。
後はアンカーを取得すれば、メモの追加を処理するリスナー関数に click イベントをバインドすることができます (リスト 7 を参照)。
リスト 7. イベントをバインドする
load : function() {
...
SA.addEvent(anchorSelected, "click", SA.addNote, false);
}
|
リスト 7 には、click イベントに関連付けられたイベント・リスナーの名前は、SA.addNote であることが示されています。この関数には以下の目標があります。
- 最後に作成されたメモを複製すること
- 複製したメモに、ユーザーが入力したテキストを注入すること
- メモのリストに、新しく作成されたメモを追加すること
リスト 8 に、最初の目標を達成するための実装を記載します。
リスト 8. 最後に作成されたメモを複製する
addNote : function(event) {
var notes = document.getElementById("notes");
// Clone the node
var newNode = notes.children[0].cloneNode(true);
},
|
getElementById メソッドでメモ ID を基準に div タグを取得した後は、この div
タグ内部にネストされた最初の子要素を取得し、cloneNode メソッドを使用してその要素を複製します。複製した
DOM ノードは、newNode という変数に格納します。
次に、newNode 内にネストされたパラグラフ・ノードを選択し、複製したノードで getElementsByTagName メソッドを呼び出します。DOM には、ノードのコンテンツを取得するために textContent
という属性が用意されていますが、残念ながらすべてのブラウザーがこの属性を完全にサポートしているわけではありません。そこで別の手法として、パラグラフから firstChild 属性にアクセスし、この属性の nodeValue
プロパティーを取得します。この取得した nodeValue に、ページ内の textarea
タグのコンテンツを設定します。textarea のコンテンツは、getElementById メソッドを使って textarea DOM 要素の
value プロパティーから取得します。複製したメモに、ユーザーが入力したテキストを注入する方法をリスト 9 に示します (2 番目の目標)。
リスト 9. 複製したメモに、テキスト域のテキストを注入する
addNote : function(event) {
...
// Set the content of the node
newNode.getElementsByTagName("p")[0].firstChild.nodeValue =
document.getElementById("contentArea").value;
notes.appendChild(newNode);
}
|
3 番目の目標を達成するには、appendChild メソッドを使用して、新しく作成されたメモをメモのリストに追加します
(リスト 10 を参照)。
リスト 10. リストに新規メモを追加する
addNote : function(event) {
...
notes.appendChild(newNode);
}
|
最後に、click イベントに対してデフォルトの振る舞いが行われないようにしなければなりません (アンカーの場合、デフォルトの振る舞いでは、href 属性に指定された URL にユーザーがリダイレクトされます)。DOM
でこのタスクを達成する方法として指定しているのは、イベントに preventDefault()
を適用し、ハンドラー関数にパラメーターを渡すことです。しかし、このメソッドにしても、バージョン 9 より前の IE ではサポートされていません。バージョン 9 より前の
IE で同じ目標を達成するには、event.returnValue 属性を false
に設定するという方法を使えます。リスト 11 にデフォルトの振る舞いを阻止するためのコードを記載します。
リスト 11. click イベントに対するデフォルトの振る舞いを阻止する
addNote : function(event) {
...
event.preventDefault ? event.preventDefault() : event.returnValue = false;
}
|
リスト 12 に、script.js ファイルに含まれる JavaScript コードをすべて記載します。
リスト 12. Script.js
window.SA = {
addEvent : function(element, evType, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener (evType, fn, useCapture);
return true;
} else if (element.attachEvent) {
var r = element.attachEvent('on' + evType, fn);
return r;
} else {
element['on' + evType] = fn;
}
},
load : function() {
var anchorSelected;
if (document.getElementsByClassName) {
anchorSelected = document.getElementsByClassName("add")[0];
} else {
var anchors = document.getElementsByTagName("a"),
alenght = anchors.length;
for (var i = 0; i < alenght; i++ ) {
var anchor = anchors[i];
if (anchor.className === "add") {
anchorSelected = anchor;
}
}
}
SA.addEvent(anchorSelected, "click", SA.addNote, false);
},
addNote : function(event) {
var notes = document.getElementById("notes");
// Clone the node
var newNode = notes.children[0].cloneNode(true);
// Set the content of the node
newNode.getElementsByTagName("p")[0].firstChild.nodeValue
= document.getElementById("contentArea").value;
notes.appendChild(newNode);
event.preventDefault ? event.preventDefault() : event.returnValue = false;
}
}
SA.addEvent(window, "load", SA.load, false);
|
開発者が JavaScript コードを作成するときには、JavaScript ライブラリー (つまりフレームワーク) を利用して、ブラウザーによって異なる DOM の実装を処理することがよくあります。例えば、よく使用されている jQuery ライブラリーを使って作成し直したリスト 13 を見てください。
リスト 13. Script-jquery.js
$(function(){
$('a.add').click(function(){
var newNote = $('.note').eq(0).clone();
newNote.find('p').text($('#contentArea').val());
$('#notes').append(newNote);
return false;
});
});
|
このように、行数がかなり減って、コードが簡潔になります。
jQuery を使用するには、このライブラリーを HTML にインポートする必要があるため (リスト 14 を参照)、HTTP リクエストを 1 つ追加で実行することになり、ライブラリーを実行するための時間が余計に必要になります。このプロセスによってアプリケーションの動作が遅くなる可能性があるので、ライブラリーを使用して、どれだけ作成するコードを削減するかは、開発者の判断次第です。
リスト 14. HTML へのライブラリーのインポート
<html>
<head>
...
<script src="http://ajax.googleapis.com/ajax/libs/jquery
/1.6.1/jquery.min.js"></script>
</head>
|
JavaScript ライブラリーは、開発者の作業を楽にする非常に強力なツールです。ただし、ライブラリーを使うことが必ずしも最も効率的な DOM の処理方法になるとは限らないため、DOM スクリプティングについての知識を持っていなければなりません。また、ライブラリーの背後で何が行われているかについても学んでおくことをお勧めします。
DOM は、JavaScript を使って Web ページにアクセスする手段となるため、Web 開発者にとって重要です。ブラウザー・ベンダーによる DOM API
の実装には、いくつかの問題と制約事項があり、属性およびメソッドの一部 (addEventListener()、textContent など) は、すべてのブラウザーで完全にサポートされているわけではありません。また、ブラウザー間で振る舞いが共通していない場合もあります。
DOM スクリプティングでは、パフォーマンスも重要な考慮事項です。この記事で具体的に説明したように、いくつかの JavaScript フレームワークを利用して DOM を操作およびトラバースすることができますが、それには JavaScript と DOM がやりとりする方法を知っておかなければなりません。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Article source code | DomScripting.zip | 5KB | HTTP |
学ぶために
- DOM Level 2 Core で定義している JavaScript
バインディングについて調べてください。
- W3C
DOM Level 2 Core Specification を読んでください。
- チュートリアル「Understanding
DOM」(developerWorks、2007年3月) で、DOM 文書の構造を調べてください。
- 「JavaScript and the
Document Object Model」(developerWorks、2002年7月) を読んで、JavaScript で DOM
を使用する方法、そしてユーザーがメモを追加し、メモの内容を編集できる Web ページの作成手順を学んでください。
- JavaScript をこれから使い始めるには、JavaScript Tutorial (w3schools)
を一読してください。
- 「JavaScript
言語入門」(developerWorks、2011年4月) では、初心者向けに JavaScript
の基本となる概念を紹介し、これらの概念をサンプル・コードで具体的に説明しています。
- Dev Guru で提供している JavaScript
言語の包括的なガイドにアクセスしてください。
- Douglas Crockford による「Classical Inheritance in
JavaScript」を読んで、JavaScript 言語の詳細を学んでください。
- 「Dynamic Content with
DOM-2」を読んで、DOM スクリプティングについての詳細を学んでください。
- W3C DOM Level 3
Events Specification を読んでください。
- 『ハイパフォーマンスJavaScript』(Nicholas
C. Zakas著、(O'Reilly Media) の第 3 章で、DOM スクリプティングについて詳しく説明しています。
- IE9
での DOM Level 3 Events サポートについての詳細を調べてください。
- developerWorks Web development
ゾーンでは、多種多様な Web ベースのソリューションを話題にした記事を揃えています。
- developerWorks
podcasts ではソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
- developerWorks
の Technical events and webcasts: developerWorks technical events and webcasts
で最新情報を入手してください。
製品や技術を入手するために
- IBM
のソフトウェアを無料で試してみてください。試用版をダウンロードすることも、オンライン評価版にログインすることも、Sandbox 環境で製品を操作することも、クラウドを介して IBM 製品にアクセスすることもできます。100 を超える IBM 製品の評価版のなかから選ぶことができます。
議論するために
- 今すぐ developerWorks
で自分のプロフィールを作って、JavaScript に関するウォッチ・リストをセットアップしてください。developerWorks
コミュニティーとずっとつながっていられます。
- Web
開発に興味を持つ他の developerWorks メンバーを見つけてください。
- Web
のトピックを専門とする developerWorks グループに参加して、知識を共有してください。
- Roland Barcia が彼のブログで Web
2.0 とミドルウェアについて語っています。
- developerWorks のメンバーが共有する
Web 関連のブックマークをフォローしてください。
- Web 2.0 Apps
フォーラムで、素早く回答を得てください。
- Ajax
フォーラムで、素早く回答を得てください。

Sebastiano Armeli-Battana は、JavaScript および Java 開発を専門とするソフトウェア・エンジニアです。Web 技術に情熱を傾けている彼は、Java 技術を採用している SMS Management & Technology でコンサルタントを務める傍ら、フリーランスの Web エンジニアとしても働いています。彼の著書には、『jQuery plug-in JAIL』があります。彼個人のサイトは http://www.sebastianoarmelibattana.com です。