JavaScript
jQuery

脱 jQuery の為の Dom 事情

More than 1 year has passed since last update.

ってことで、超今さら感たっぷりかもだけど 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 Developper 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() あたりで。