サイ本15章ドキュメントの制御

友達と一緒に読み進めて気づいたら15章まで来てました。
JavaScript 第5版
第13章第2部以降はJavascriptの言語そのものではなく、ブラウザ実装におけるJavascriptの話です。例えばサーバサイド実装であるNode.js等には関係のない話ですが、Javascriptを語る上でブラウザ上のクライアントサイドスクリプティングは避けて通れないので、ある意味ではこれまでより重要な章になります。

ブラウザにおけるJavascriptで特に重要なのがこの章のDOMの話で、静的なhtml構造を動的に解釈・操作するための基礎知識になります。DOM自体はJavascriptの実装に依存するものではなく、xmlやhtmlドキュメントを操作するためのインターフェースとしてW3Cで定義されているもので、他の言語からこれらのドキュメントを操作する上でも役に立つ知識です。

では前置きをこのくらいにして

「ドキュメントの制御」……ドキュメントって?

この章のタイトルで指している「ドキュメント」はJavascriptで言う

window.document
// windowはグローバルオブジェクトなので以下の形でも参照できる
document

を指しています。このオブジェクトを操作することで、ページ内のすべての構造や要素を変更することができるようになります。その核となる(DOMを表す)プロパティがdocument.documentElementですが、ここでは先にそれ以外のプロパティを見ていきます。

document.* プロパティ

レガシーなプロパティとして紹介されているものとして以下のようなものがあります。レガシーと言いつつ、bgColor以外は基本的には現在でも使うものです。

document.bgColor
ページ全体の背景色を表す。使うな
document.cookie
cookieの取得や操作。普通に使え
document.domain
現在のページのドメインを取得、または同一出身ポリシー制限を緩和するために設定する(後述)
document.lastModified
ドキュメントの最終更新日。htmlファイルであれば通常そのファイルの更新日。動的ページの場合はwebサーバが出すLast-modifiedヘッダの値(参考
document.referrer
リファラURL(このページに来る前にいたURL)
document.title
現在のページタイトル
document.URL
現在のページのURL(リダイレクトされた場合はリダイレクト後のURL),document.location.href(リダイレクトされた場合はリダイレクト前のURL)

document.domainはあの説明じゃ分からないと思うので、軽く説明を。以下のようにhostA, hostBと2つの異なるホストでサイトを運営している場合に、それぞれのサイト間で動的に連携したい場合を想定しています。

hostAからロードされたスクリプトがhostBのドキュメントをロードしたい
→同一出身ポリシーに違反する(document.domainが異なる)のでダメ

そんなときにそれぞれのdocument.domainを設定します。

document.domain = "example.com";
// 指定できるのは元々のdomainのサフィックス、かつピリオドを1つ以上含む文字列

レガシーDOM

DOMはドキュメントを操作するためのインターフェースと初めに言いましたが、現在DOMと呼んでいるものは基本的にW3Cで標準化されているDOMを指しています。ここでは、標準化される以前に使われていたものを超簡単に紹介します。ググったときに出てくる古い紹介ページなどで見かけたら( ´_ゝ`)フーンと思ってください。

document.anchors[]
anchor(aタグのname属性)リスト
document.applets[]
javaアプレットリスト
document.forms[]
formタグリスト
document.images[]
imgタグリスト
document.links[]
link URL(aタグのhref属性)リスト

最初から配列として取得できるのは便利な面もありますが、W3C DOMではこれらを一般化してより柔軟に操作できるようになっているので、今後は基本的に使わないほうが良いでしょう。

W3C DOM

さて、ようやくW3C DOM(以下単にDOMと書きます)です。DOMを大雑把に言うとツリー構造で表現できるドキュメントを操作するためのインターフェースです。そして、現在ツリー構造で表現するドキュメントと言えばxmlとかhtmlです。pythonphpといった様々な言語でdomを扱えるライブラリが存在していますが、それらは基本的にこの共通のインターフェース定義に基づいて実装されています(はずです)。

document.documentElementがツリーのルートを表しており、その下は以下のようにご存知のhtmlタグがツリー上に並びます。bodyだけなんかショートカットがありますが、一般的にDOMを操作する上でよく使うのがhtmlの中のbodyだから、というだけの理由みたいです。

一つ一つの要素はすべてNodeであり、特にhtmlのタグを表す要素についてはHTMLElementノード、テキストデータを表す要素についてはTextノードのインターフェースを実装したオブジェクトになっています。DOMにおけるインターフェースの関係を一部抜粋すると以下のようになります。

Nodeには自分の前に一つ要素を追加するinsertBeforeや、自分の子要素に追加するappendChildといったツリー構造を操作する汎用メソッドが用意されています。HTMLElementは単にhtmlタグ全体に共通で定義されているid,styleといった属性のプロパティが追加されているだけなので、基本的にはDocument, Node, Elementあたりを把握しておけば十分でしょう。


だらだらと書いてきましたが、具体的にどんなコードになるかというとこんなコードです。

// ノードの取得
var n = document.documentElement; // htmlタグを表す
var children = n.childNodes; // head, bodyノードのリスト
var body = children[1];

// ノードの作成・挿入
var t = document.createTextNode("text node");
body.appendChild(t);

別に大したことはありませんね。HTMLのツリー構造をイメージできれば問題なく使えるはずです。他にどんなメソッドが使えるかは、W3Cの定義をご覧ください。DOMには1から3までのレベルがあり、ここで話した内容についてはDOM Level1で十分です(レベルはバージョンとは違い、ライブラリ側でどこまでを実装するかという話なので、Level 1が古いといったわけではありません)
Document Object Model Core


なぜここでインターフェースの詳細を紹介したくないかと言えば、もうDOMの操作なんてjQuery使えばいいじゃん、と言いたいからです。createElement, getElementById, getElementsByTagName, getElementsByNameといったメソッドは当然jQueryの内部で使われているわけですが、こんな長い関数名をだらだら打って覚えるくらいだったら、jQueryのメソッドの使い方を1つでも覚えたほうがスマートにDOMを操作できます。


というわけでぶん投げて以上!

おまけ:document.write

Javascripthello worldといえば、おそらく以下の形で書くと思います。

document.write("hello world");

ご覧のとおり document.write は簡単に動的な出力を実現することができる便利関数ですが、この関数は使い方によってはちょっと曲者なので解説しておきます。

大事なこと

document.writeを使ってページ内に何を出力できるのはページ読み込みと同時に実行できるscriptのみ。イベントハンドラによってdocument.writeを実行すると、ページが真っ白になってしまう。

わかりづらいと思うので、例を書きます。

問題ない例
<html>
<head></head>
<body>
<script>
document.write("hello ");
</script>
world
</body>
</html>

hello worldと表示されます。

問題ある例
<html>
<head></head>
<body>
<button onclick='document.write("hello ")'>click</button>
world
</body>
</html>

ボタンとworldだけが表示されている状態でボタンをクリックすると、真っ白い画面に hello とだけ表示されます。

document.writeの謎

なぜこういうことになるのか、Javascriptで新しいウィンドウを作成してその中に文字を出力する例を見て考えてみましょう。以下の例では、新しいウィンドウを作成してその中に Hello World!と表示しています。

function hello() {
 var w = window.open();
 var d = w.document;

 // phase1
 d.open();

 // phase2
 d.write("<h1>Hello World!</h1>");

 d.close(); // ドキュメントを閉じ、画面に表示される
 // phase3
}

先ほどの「問題ない例」はhtml"出力中"の document.write であり、上記の phase2 で実行した場合に相当します。一方「問題ある例」はhtmlの出力が終わった(closeされた)後のイベントハンドラによる document.write であり、上記の phase3 に相当します。さて、ここからは document.write の仕様ですが、1度 close された document 上で document.write すると、出力する前にその document を再度 open します。そして、open するとその document はまっさらな状態になるため、結果的に真っ白な画面に指定した文字列だけが寂しく残る、という表示になるのです。