DOMのまとめ


参照

パーフェクトJavaScript


DOM


  • DOMとは、HTMLドキュメントやXMLドキュメントをプログラムから利用するためのAPI。


  • DOMではHTMLドキュメントやXMLドキュメントを「オブジェクトのツリー状の集合」として取り扱う。このツリーをDOMツリーと呼ぶ。


  • DOMツリーの一つ一つのオブジェクトはノードと呼ばれる。ある1つのノードから他のノードを参照する時は、親ノード、子ノード、兄弟ノード、先祖ノード、子孫ノードなどと呼ぶ。


  • DOMの仕様は、Level1〜3がW3Cにより定義されている。



タグと要素とノード


  • タグ (文書構造を指定するためのマークアップとして記述する文字列)

  • 要素とノード (要素とノードは継承関係 ノードがスーパータイプ) 


DOM操作

[なぜやるのか]


  • インタラクティブな機能


  • ある特定のDOM操作を選択しそのDOMの内容や属性を書き換えたり、新しい機能のDOM要素を作成したりして、ユーザーに視覚的なフィードバックを与える。


[手順]


  • 1.ノードの選択

  • 2.ノードの作成

  • 3.ノードの変更

  • 4.ノードの削除


Documentオブジェクト


  • DOMツリー構造のルートノード


  • Documentオブジェクトは、そのHTMLドキュメント全体を表現するオブジェクト


  • DocumentオブジェクトはJavaScriptにおいて、documentというグローバル変数でアクセスできる。



1.ノードの選択

var element = document.getElementById('foo');


  • fooというIDを持つ要素を取得。


  • IDはDOMツリーの中で一意でなければならない。



タグ名による検索とワイルドカードによる選択

//ドキュメント全体からspan要素だけを取得する

var spanElements = document.getElementByTagName('span');

//ドキュメント全体から全ての要素を取得する
var allElements = document.getElementByTagName('*');

Document.getElementById()は、オブジェクトだけのメソッドだったが、

Element.getElementByTagName()は、DocumentオブジェクトとElementオブジェクト両方のメソッド。

あるElementオブジェクトのgetElementByTagName()メソッドを実行した場合、そのElementオブジェクトの子孫ノードの中から特定のタグ名を持つ要素が取得される。

<body>

<p id="foo">
<span>a</span>
<span>b</span>
<span>c</span>
</p>

<p id="bar">
<apan>x</span>
</p>

<script>

//fooの子孫ノードの中からspan要素を取得する
var fooSpans = foo.getElementsByTagName('span');
alert(foospans.length); //3

//ドキュメント全体からspan要素を取得する
var allSpans = document.getElementsByTagName('span');
alert(allspans.length); //4

</script>


ライブオブジェクト


<div id="foo">
 <span>first</span>
<span>second</span>
</div>

<script>
 var elems = document.getElementByTagName('span');
 alert(elems.length); //2

var newspan = document.createElement('span');
newSpan.appendChild(document.createTextNode('third'));

var foo = document.getElementById('foo');
foo.appendChild(newSpan);
alert(elems.length); //3

</script>

なぜ、spanを追加する前に取得していたNodeListオブジェクトがspanを追加した後の状態も知っているのか?

ライブオブジェクトは、常に、DOMツリー実体への参照を持っているから、

DOMツリーへ加えられた変更は、ライブオブジェクトからも参照できる。


ライブオブジェクトを操作する上での注意点


罠.html


//ライブオブジェクトの罠

<div>sample text</div>

<script>
var divs = document.getElementByTagName('div');
var newDiv;

for(var i = 0; i < divs.length; i++){
 newDiv = document.createElement('div');
newDiv.appendChild(document.createTextNode('new div'));
divs[i].appendChild(newDiv);
}

</script>



  • div.lengthの値が1つ大きくなり、ループが抜けられなくなる


正しい.html


<div>sample text</div>

<script>
var divs = document.getElementByTagName('div');
var newDiv;

//divs.lengthを最初に取得しておき、それをループ継続条件に使う

for(var i = 0; len = divs.length; i < len; i++){
 newDiv = document.createElement('div');
newDiv.appendChild(document.createTextNode('new div'));
divs[i].appendChild(newDiv);
}

</script>



ライブオブジェクトのパフォーマンス


  • ライブオブジェクトは、パフォーマンスを考えると不利な実装

  • 一度Arrayに変換してから使う場合の方がパフォーマンスが優れている

  • Arrayに変換するにはArray.prototype.slice()メソッドをNodeListオブジェクトに対して適用する

var nodeList = document.getElementByTagName('span');

//NodeListオブジェクトをArrayオブジェクトに変換する
var array = Array.prototype.slice.call(nodeList);

(※IE8以前ではエラーになる)

NodeListをforループで扱う場合は、Arrayオブジェクトに変換してから利用した方が良い(P255)


クラス名による検索

HTMLElement.getElementsByClassName()メソッド


関連するノードを参照するためのプロパティ

スクリーンショット 2016-03-05 23.47.52.png

(※P258)


XPath

例) 「mainというidが指定されたdiv要素の中にある、contentというclassが指定された3番目のp要素」

(※P261)

evaluateメソッドの第4引数の値と返値の関係

イテレータとスナップショットの違い


Selectors API

CSSで要素を指定する時と同様の指定方法で要素が取得できる

querySelectorAll() 該当する全てを取得

querySelector() 複数の要素が該当したとしても最初の一要素だけ返す


var a = document.querySelector('#foo');
var b = document.getElementById('#foo');
alert(a === b); //true

var c = document.querySelectorAll('div');
var d = document.getElementByTagName('div');
alert(c[0] === d[0]); //true

querySelectorAll()で取得されるオブジェクトはStaticNodeListオブジェクト。

「NodeListリスト」変更を加えるとHTMLドキュメントに反映

「StaticNodeListオブジェクト」 変更を加えてもHTMLドキュメントに反映されない

StaticNodeListオブジェクトに対して変更を加えてもHTMLドキュメントには反映されないことに注意。


2.ノードの作成


Document.createElement()メソッド
Document.createTextNode()メソッド

ノードを作成しただけではHTMLドキュメントにとって何の変化もない。

作成したノードをDOMツリーに追加して初めてWebブラウザに表示される。


var elem = document.createElement('div'); //div要素を作成
var text = document.createTextNode('テキストノードを作成する');
document.body.appendChild(elem); //body直下にdiv要素を追加
elem.appendChild(text); //作成したdiv要素にテキストノードを追加

var comment = document.createComment('コメントノード');
document.body.insertBefore(comment,elem); //elemの前にコメントノードを挿入


3.ノードの内容変更


var newNode = document.createElement('div');
var oldNode = document.getElementById('foo');
var parentNode = oldNode.parentNode;
parentNode.replaceChild(newNode,oldNode);


4.ノードの削除

var elem = document.getElementById('foo');

elem.parentNode.removeChild(elem);




もっと簡単にHTMLを変更する innerHTML


  • HTML5の仕様

var elem = document.getElementById('foo');

elem.innerHTML = '<div>This is a new div element.</div>';


textContent

var elem = document.getElementById('foo');

elem.textContent = '<div>Is this a new div element?</div>';

div要素は作成されない このまま文字列としてブラウザに表示される


DOM操作のパフォーマンス


パフォーマンスが悪い.js

var parent = document.getElementById('parent');

for (var i = 0; i < 10; i++){
var child = document.createElement('div');
parent.appendChild(child);
}


DocumentFragmentを利用して再描画を1回にする


パフォーマンスが良い.js

var fragment = document.createDocumentFragment();

for(var i = 0; i < 10; i++){
var child = document.createElement('div');
//DocumentFragmentに対して子要素を追加する
fragment.appendChild(child);
}

//実際に追加されるのはDocumentFragmentの小要素だけ
document.getElementById('parent').appendChild(fragment);