脱 jQuery の為の Dom 事情

ってことで、超今さら感たっぷりかもだけど Dom やるよ。

* getElementsByName みたいなあまり使わないものは省くよ


手始めにノードの選択あたりから


サンプル HTML

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Dom Sample</title>
</head>
<body>

<div id="header">
<h1><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></h1>
<ul>
<li><a href="#">Site Map</a></li>
<li><a href="#">Login</a></li>
</ul>
</div>

<div id="nav">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Documentation</a></li>
<li><a href="#">Download</a></li>
<li><a href="#">Plugins</a></li>
<li><a href="#">Support</a></li>
</ul>
</div>

<div id="content">
<h2>What is XXX?</h2>
<p class="text">
Sit delectus laboriosam in alias maxime ad accusamus. Delectus provident molestias voluptates quibusdam voluptas dolor alias. Nam eos qui incidunt molestiae modi voluptas earum sint autem dolores accusamus molestiae aliquid.
</p>
<p class="text">
Amet quae perspiciatis dolor quos incidunt amet quaerat sunt itaque eos cupiditate eius voluptate eum. Animi enim rerum voluptates magnam obcaecati eveniet libero. Ea labore aut hic nisi temporibus animi?
</p>
<p class="text">
Consectetur commodi quibusdam sed veniam in accusamus aperiam, voluptatum quasi quaerat eligendi! In sint adipisci ut exercitationem sit expedita, modi porro harum molestiae magni impedit. Accusantium ipsum ea eaque nisi.
</p>

<h2>Corporate Members</h2>
<ul>
<li><a href="#">Google</a></li>
<li><a href="#">Yahoo</a></li>
<li><a href="#">Apple</a></li>
<li><a href="#">Microsoft</a></li>
</ul>

<h2>API</h2>
<div class="LImgRTextWrap">
<dl>
<dt><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></dt>
<dd>Elit distinctio eveniet porro iste cumque. Atque voluptatum ducimus impedit sed eveniet quo beatae quos. Quam laudantium ad ipsam veritatis quam. Eius suscipit at autem nesciunt aliquid sequi laudantium cum.</dd>
</dl>
</div>

<div class="RImgLTextWrap">
<dl>
<dt>Ipsum voluptatibus cumque perspiciatis possimus possimus molestias! A tempora enim fugit quam voluptatem deserunt perspiciatis exercitationem voluptatibus! Et et adipisci accusantium officia necessitatibus magni necessitatibus ut! Eligendi fugit veritatis reiciendis!</dt>
<dd><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></dd>
</dl>
</div>

<div class="wrap">
<div class="left"></div>
<div class="right">
<div id="uniqBox"></div>
</div>
</div>

<div id="side">
<h3>Banner</h3>
<ul>
<li><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></li>
<li><a href="#"><img src="http://placekitten.com/300/300?image=14" alt="sample"></a></li>
<li><a href="#"><img src="http://placekitten.com/400/300?image=15" alt="sample"></a></li>
</ul>
<p class="text">
Elit iure rem voluptatem cupiditate ducimus repudiandae voluptatibus enim delectus nisi eos ipsam molestias delectus? Assumenda tempora cumque pariatur blanditiis ratione ex qui nesciunt error. Ipsum neque nemo illo officia.
</p>
</div>
</div>

<div id="footer">
<h2>Books</h2>
<ul>
<li><a href="#">Learning XXX</a></li>
<li><a href="#">XXX First Step</a></li>
<li><a href="#">Programming XXX</a></li>
</ul>
<p>Copyright 2015 XXX Foundation. XXX License</p>
</div>

</body>
</html>


IDによる指定


// jQuery
var nav = jQuery("#nav");

// Dom

var nav = document.getElementById("nav");


タグ名(要素名)による指定

// jQuery

var divs = jQuery("div");
console.log( divs.length );

7

//dom

var divs = document.getElementsByTagName("div");
console.log( divs.length );

7


クラス名による指定

// jQuery

var txts = jQuery(".text");
console.log(txts)

[p.text, p.text, p.text, p.text, prevObject: n.fn.init[1], context: document, selector: ".text", jquery: "2.1.4", constructor: function]

// dom

var txts = document.getElementsByClassName("text");
console.log(txts);

[p.text, p.text, p.text, p.text, item: function, namedItem: function]


親を指定

// jQuery

var targetEle = jQuery("#uniqBox");
console.log(targetEle.parent());

[div.right, prevObject: n.fn.init[1], context: document, jquery: "2.1.4", constructor: function, selector: ""]

// dom

var targetEle = document.getElementById("uniqBox");
console.log(targetEle.parentNode);

/* parentNode で親要素が返ってくる
<div class="right">​…</div>
*/


嗚呼、closest、嗚呼、closest

jQuery の closest() がすごく便利だったんだけど、jQuery でない方の closest() は IEで使えないっぽい。IEを考えなくてい良い状況ならば使えるかもしれない(参考:Mozilla Developer Network : Element.closest())。スマホでどうなのか、Safariでどうなのかっつー検証する価値はあるな。

// jQuery

var targetEle = jQuery("#uniqBox");
console.log(targetEle.closest('.wrap'));

[div.wrap, prevObject: n.fn.init[1], context: document, jquery: "2.1.4", constructor: function, selector: ""]


var targetEle = document.getElementById("uniqBox");
console.log(targetEle.closest('.wrap'));

/*
<div class=​"wrap">​…​</div>​
*/


子を指定 - childNodes

// jQuery

var targetEle = jQuery('.wrap');
console.log(targetEle[0].children);

[div.left, div.right, item: function, namedItem: function]


// dom
var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].childNodes);

[text, div.left, text, div.right, text, item: function]


子を指定 - firstElementChild

// jQuery

var targetEle = jQuery('.wrap :first');
console.log(targetEle);

[div.left, prevObject: n.fn.init[1], context: document, selector: ".wrap :first", jquery: "2.1.4", constructor: function]

// dom + Element Traversal API

var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].firstElementChild);

/*
<div class=​"left">​</div>
*/


firstChild や nextSibling などの直接的な指定をする場合の注意

parentNode や childNodes はあくまでも Node を取得するため用意されたプロパティです。要素でなく Node ね。

Node には 要素とは違い「空白文字」や「改行」も「テキストノード」として扱われているよ。

そして、firstChild や lastChild、nextSibling や previousSibling はテキストノードも取ってきちゃうプロパティなんだ。

たとえば、さっきのこの例の最初の要素は text になっているよ


// dom
var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].childNodes);

[text, div.left, text, div.right, text, item: function]

この最初の要素を出力してみるとこうなるよ


var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].childNodes[0]);

baseURI: (...)
childNodes: (...)
data: "↵ "
firstChild: (...)
lastChild: (...)
length: 4
localName: null
namespaceURI: null
nextElementSibling: div.left
nextSibling: (...)
nodeName: "#text"
nodeType: 3
nodeValue: "↵ "
ownerDocument: (...)
parentElement: div.wrap
parentNode: (...)
previousElementSibling: null
previousSibling: (...)
textContent: "↵ "
wholeText: "↵ "

こので注目なのが length 。4となっているということは 4文字 存在しているということ。chrome の developer tool で dataプロパティや、textContentプロパティを見ると「改行文字1つ」と「タブ文字3つ」が含まれているのがわかる。

要するに、次の要素までの文字列全てが テキストノードとして扱われている。もし、意図した動き(たとえば要素を取りたいなど)でない場合はこの点に注意が必要。


要素だけを取るためには

children プロパティや Element Traversal API を利用するよ。さっきの例がまさにそれ。

// dom + Element Traversal API

var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].firstElementChild);

/*
<div class=​"left">​</div>
*/

ついでに children を使ってみる

var targetEle = document.getElementsByClassName('wrap');

console.log(targetEle[0].children);

[div.left, div.right, item: function, namedItem: function]

便利だね。

ってことで Element Traversal API の一部を挙げるよ

プロパティ
取得できる要素

firstElementChild
最初の子要素

lastElementChild
最後の子要素

nextElementSibling
次の要素

previousElementSibling
前の要素


Selectors API

querySelector() や querySelectorAll() を利用することによってより直感的な dom の取得ができるようになるよ。

// Selectors API

var targetEle = document.querySelector("#nav");
console.log(targetEle);

/*
<div id=​"nav">​…​</div>​
*/

// Selectors API

var targetEle = document.querySelectorAll("#nav li");
console.log(targetEle);

/*
[li, li, li, li, li, li, item: function]
*/

CSS のセレクター的な指定の仕方ができるのでより直感的だよね


ノードの作成

ノードを作成し反映するには 「作成」→「DOMツリーに追加」の操作が必要です。

* 見ていて胸焼けするコードがちょっと連発します


ノードの作成

var addLiEle = document.createElement('li');

var addAEle = document.createElement('a');
var addImg = document.createElement('img');


属性追加

属性の追加には、「定義」→「値の設定」の操作が必要

var hrefAttr = document.createAttribute('href');

hrefAttr.value = "#";
var srcAttr = document.createAttribute('src');
srcAttr.value = "http://placekitten.com/g/500/500?image=12";

属性を要素に設定

addAEle.setAttributeNode(hrefAttr);

addImg. setAttributeNode(srcAttr);


ノードの追加

addAEle.appendChild(addImg);

addLiEle.appendChild(addAEle);
targetEle.appendChild(addLiEle);

まとめるとこんな感じ

var targetEle = document.querySelectorAll("#side ul")[0];

var addLiEle = document.createElement('li');
var addAEle = document.createElement('a');
var addImg = document.createElement('img');
var hrefAttr = document.createAttribute('href');
hrefAttr.value = "#";
var srcAttr = document.createAttribute('src');
srcAttr.value = "http://placekitten.com/g/500/500?image=12";

addAEle.setAttributeNode(hrefAttr);
addImg. setAttributeNode(srcAttr);

addAEle.appendChild(addImg);
addLiEle.appendChild(addAEle);
targetEle.appendChild(addLiEle);


ノードの削除

ノードの削除には removeChild を使うよ。

var targetEle = document.querySelectorAll("#side ul li")[1];

console.log(targetEle);
var parentEle = targetEle.parentNode;
parentEle.removeChild(targetEle);

querySelector を使って親ノードを指定してもよいよ

var targetEle = document.querySelectorAll("#side ul li")[1];

console.log(targetEle);
var parentEle = document.querySelector("#side ul");
parentEle.removeChild(targetEle);


innerHTML

createElement()やappendChild() の連発はさすがに胸焼けするよね。この胸焼けを解消する1つに innerHTMLプロパティ があるよ。

var newDom = '<li><a href="#"><img src="http://placekitten.com/g/200/300?image=1"></a></li>'+

'<li><a href="#"><img src="http://placekitten.com/400/500?image=3"></a></li>'+
'<li><a href="#"><img src="http://placekitten.com/200/100?image=5"></a></li>';

var targetEle = document.querySelector("#side ul");
targetEle.innerHTML = newDom;


NodeList と StaticNodeList

getElementsByTagName() の様に、ノード取得系メソッドの中には NodeList インターフェースを実装しているものもあるよ。

このインターフェースは item() メソッド と length プロパティ が定義してあり、item() メソッドは引数にインデックスを渡せば DOMツリーて定義されている順の1つにアクセスすることができるよ。

また、length プロパティは その要素数を返すよ。次のコードを見てみよう

//dom

var pTags = document.getElementsByTagName("p");
console.log(pTags);

[p.text, p.text, p.text, p.text, p, p, p, item: function, namedItem: function]

item() という関数を確認できる。length に関しても確認してみよう。

var targetTags = document.getElementsByTagName("p");

console.log(targetTags.length);

5

このポストで使用している サンプル HTML では p 要素が 5つあるので。思い通りの結果が返ってきている。では次のコードはどうでしょう。

var targetWrap = document.getElementById("content");

var targetTags = document.getElementsByTagName("p");

console.log(targetTags.length);
5

var addEle = document.createElement('p');
targetWrap.appendChild(addEle);

console.log(targetTags.length);
6

getElementsByTagName() で取得しなおしていないのに、appendChild で足した分も2回目の targetTags.length で反映されている。

これは NodeList は Live Correction でもあり、常に DOMツリー への参照をを持っているからで、取得時には最新の情報を取得できるわけです。

これに対し、querySelectorAll()で取得されるオブジェクトは StaticNodeList です。StaticNodeList は Live Correction ではないので、オブジェクトに対して加えた変更は HTML ドキュメントに反映されません。

したがって上記の length のテストのようなことを行っても、思い通りの結果は返ってきません。

var targetWrap = document.getElementById("content");

var targetTags = document.querySelectorAll("p");

console.log(targetTags.length);
5 // 初期状態は 5

var addEle = document.createElement('p');
targetWrap.appendChild(addEle);

console.log(targetTags.length);
5 // StaticNodeList なので 要素が追加されても 5

targetTags = document.querySelectorAll("p");
console.log(targetTags.length);
6 // 新たに取得し直すと最新の状態

さしずめよく使いそうなメソッドで NodeList を返すのは、getElementsByTagName()やchildNodesくらいで、逆に StaticNodeList を返すのは querySelectorAll() あたりで。