#はじめに
JavaScriptで要素を取得する時に使う時 jQuery などの便利系を除いていろいろありますが、それらの関係情報や使い方のメモです。
#前知識
要素を取得する前にいろいろ知っておきましょう。
噛み砕いた簡単な説明なので、ツッコミはなしの方向で……
##Node
ノードとは、簡単に言うとDOMを構成する文字列、コメント、タグなどを扱うためのインターフェース(様々な処理を行うメソッドが含まれたオブジェクトと考えるといいかも)です。
<!DOCTYPE html>
も <!-- コメント -->
も <span>Text</span>
のspan要素も、その中のテキストもひとつのノードです。
##Element
, HTMLElement
, HTML*Element
要素、エレメント、いわゆるHTMLなどのタグを示すインターフェースです。
Node
を継承しています。
###Element
主に XML で使用される要素です。Node
を親に持つインターフェースです。
XML では独自の要素を作れますが、主要なものでは <xml>
や <svg>
などが当てはまります。
###HTMLElement
汎用的な HTML の要素です。Element
を継承したインターフェースです。
HTML5において文章の意味を示すための要素 <header>
、<main>
、<footer>
、<article>
、<section>
、<aside>
、<nav>
などが当てはまります。
###HTML*Element
HTMLElement
を更に継承した独自のインターフェースです。
<div>
なら HTMLDivElement
、<a>
なら HTMLAnchorElement
、<h1>
~<h6>
なら HTMLHeadingElement
といった具合です。
独自のプロパティやメソッドを持ったものもありますが、HTMLDivElement
のように HTMLElement
を継承しただけで特に意味がないものもあったりします。
##HTMLCollection
現在の状態の要素の集合を表すオブジェクト(インターフェース)で、構造は配列に似ています。
###HTML*Collection
HTMLCollection
を継承した独自のインターフェースです。
これは例えば、<form>
要素内のフォーム系要素を取得する HTMLFormElement.elements
は HTMLFormControlsCollection
というものになっています。
##NodeList
DOMノードの集合を表すオブジェクト(インターフェース)で、構造は配列に似ています。
#HTMLCollection
と NodeList
の違い
一見するとどちらも要素の配列的なもので同じに思えますが、細かいところが違っています。
##HTMLCollection
の特徴
HTML要素以外のノードを含まない配列っぽいインターフェースです。
これは現在の状態のDOM内の要素をリスト化しているので、DOMに変更があった場合はリアルタイムで中身が変わります。
静的な配列のように扱いたい場合は注意しないといけません。
##NodeList
の特徴
NodeList
とはその名の通り、ノード全般を含む配列っぽいインターフェースです。
なので、要素以外にもテキストノードやコメントノードももしかしたら含まれているかもしれません。
(要素取得系のメソッドで得られる NodeList
は要素のみなのであまり心配いりません)
他にも、Array.prototype.forEach
のように NodeList.prototype.forEach
が実装されているので、配列のように簡単に処理できます。(IEは非対応)
基本的に配列のように静的なオブジェクトですが、Node.childNodes
で取得される NodeList
は HTMLCollection
のようにDOMに変更があった場合は中身が変わる可能性があります。
それでは本題
#要素が取得できるメソッド
要素のオブジェクトの型については HTMLElement
として統一して扱います。
##document.getElementById()
返り値: HTMLElement
/ null
言わずと知れた、指定した ID を持つ要素を取得します。
もし存在しなかった場合は null
が返ります。
万が一、DOMに同じIDを持つ要素が複数存在する場合は最初の要素しか取得しません。
<span id="spanText">Text</span>
var spanText = document.getElementById('spanText');
console.log(spanText); // <span>
console.log(spanText.textContent); // "Text"
var divText = document.getElementById('divText');
console.log(divText); // null
##document.getElementsByTagName()
, element.getElementsByTagName()
返り値: HTMLCollection
タグ名を指定して取得します。
HTMLCollection
なので、DOMの操作をした場合に中身が変わる可能性があります。
<ul id="textList">
<li>Text 1</li>
<li>Text 2</li>
<li>Text 3</li>
</ul>
<ul>
<li id="foo">Text 4</li>
</ul>
/* document 全体から li 要素を取得 */
var li01 = document.getElementsByTagName('li');
console.log(li01); // HTMLCollection (4) [li, li, li, li#foo]
for (var i = 0; li01.length; i++) {
console.log(li01[i].textContent);
}
// "Text 1"
// "Text 2"
// "Text 3"
// "Text 4"
/* 特定の要素内から li 要素を取得 */
var
textList = document.getElementById('textList'),
li02 = textList.getElementsByTagName('li');
console.log(li02); // HTMLCollection (3) [li, li, li]
for (var j = 0; li02.length; j++) {
console.log(li02[j].textContent);
}
// "Text 1"
// "Text 2"
// "Text 3"
##document.getElementsByClassName()
, element.getElementsByClassName()
返り値: HTMLCollection
クラス名を指定して取得します。
HTMLCollection
なので、DOMの操作をした場合に中身が変わる可能性があります。
<ul id="textList">
<li>Text 1</li>
<li class="foo">Text 2</li>
<li>Text 3</li>
</ul>
<p class="foo">Text 4</p>
/* document 全体から .foo を取得 */
var foo01 = document.getElementsByClassName('foo');
console.log(foo01); // HTMLCollection (2) [li.foo, p.foo]
for (var i = 0; foo01.length; i++) {
console.log(foo01[i].textContent);
}
// "Text 2"
// "Text 4"
/* 特定の要素内から .foo を取得 */
var
textList = document.getElementById('textList'),
foo02 = textList.getElementsByClassName('foo');
console.log(foo02); // HTMLCollection (1) [li.foo]
for (var i = 0; foo02.length; i++) {
console.log(foo02[i].textContent);
}
// "Text 2"
##document.getElementsByName()
返り値: NodeList
要素の name
属性の値を元に要素を取得します。
このメソッドはブラウザごとに動作が異なる場合があるだけでなく、HTML5においてはほとんど活躍することはないと思います。
<input type="hidden" name="foo" value="Text 1">
<input type="text" name="foo" value="Text 2">
<select name="foo">
<option>Text 3</option>
<option selected>Text 4</option>
<option>Text 5</option>
</select>
var foo = document.getElementsByName('foo');
console.log(foo); // NodeList (3) [input, input, select]
foo.forEach(function (elem) {
console.log(elem.value);
});
// "Text 1"
// "Text 2"
// "Text 4"
##document.querySelector()
, element.querySelector()
返り値: HTMLElement
CSSセレクタでマッチした最初の要素を取得します。
CSS3のセレクタの記述もブラウザが対応していれば可能です。
<p class="foo">Text 1</p>
<ul>
<li>Text 2</li>
<li class="foo">Text 3</li>
<li>Text 4</li>
<li>Text 5</li>
</ul>
<ul id="textList">
<li class="foo">Text 6</li>
<li>Text 7</li>
</ul>
/* 最初の .foo を取得 */
var foo01 = document.querySelector('.foo');
console.log(foo01); // <p>
console.log(foo01.textContent); // "Text 1"
/* 最初の ul .foo を取得 */
var foo02 = document.querySelector('ul .foo');
console.log(foo02); // <li>
console.log(foo02.textContent); // "Text 3"
/* 特定の要素内から最初の .foo を取得 */
var
textList = document.getElementById('textList'),
foo03 = textList.querySelector('.foo');
console.log(foo03); // <li>
console.log(foo03.textContent); // "Text 6"
/* 最初の li:last-child を取得 */
var lastChild = document.querySelector('li:last-child');
console.log(lastChild); // <li>
console.log(lastChild.textContent); // "Text 5"
###特定のIDの要素を取得する時
document.getElementById('foo')
よりも document.querySelector('#foo')
の方が直感的でわかりやすいかもしれませんが、getElementById()
の方が高速な処理になります。(といっても人間の感覚ではわからないですが)
##document.querySelectorAll()
, element.querySelectorAll()
返り値: NodeList
CSSセレクタでマッチした要素を取得して NodeList
として返します。
CSS3のセレクタの記述もブラウザが対応していれば可能です。
jQuery の $('selector')
に近いメソッドです。
<p class="foo">Text 1</p>
<ul>
<li>Text 2</li>
<li class="foo">Text 3</li>
<li>Text 4</li>
<li>Text 5</li>
</ul>
<ul id="textList">
<li class="foo">Text 6</li>
<li>Text 7</li>
</ul>
/* .foo を取得 */
var foo01 = document.querySelectorAll('.foo');
console.log(foo01); // NodeList (3) [p.foo, li.foo, li.foo]
foo01.forEach(function (elem) {
console.log(elem.textContent);
});
// "Text 1"
// "Text 3"
// "Text 6"
/* ul .foo を取得 */
var foo02 = document.querySelectorAll('ul .foo');
console.log(foo02); // NodeList (2) [li.foo, li.foo]
foo02.forEach(function (elem) {
console.log(elem.textContent);
});
// "Text 3"
// "Text 6"
/* 特定の要素内から最初の .foo を取得 */
var
textList = document.getElementById('textList'),
foo03 = textList.querySelectorAll('.foo');
console.log(foo03); // NodeList (1) [li.foo]
foo03.forEach(function (elem) {
console.log(elem.textContent);
});
// "Text 6"
/* li:last-child を取得 */
var lastChild = document.querySelectorAll('li:last-child');
console.log(lastChild); // NodeList (2) [li, li]
lastChild.forEach(function (elem) {
console.log(elem.textContent);
});
// "Text 5"
// "Text 7"
###特定のタグまたはクラス名を取得する時
document.getElementsByTagName('div')
よりも document.querySelectorAll('div')
、document.getElementsByClassName('.foo')
よりも document.querySelectorAll('.foo')
の方が直感的でわかりやすいかもしれませんが、それぞれ getElementsByTagName()
、getElementsByClassName()
の方が高速な処理になります。
#取得した NodeList
や HTMLCollection
の使い方
要素を直接取得できる document.getElementById()
や document.querySelector()
と違い、それ以外のメソッドに関しては NodeList
または HTMLCollection
のオブジェクトであり、そのままでは要素を扱えません。
そこで、配列のようなオブジェクトなので for
文のように反復処理をさせる必要がありますが、Array.prototype.forEach
を使うのも手です。
var forEach = Array.prototype.forEach;
var div = document.getElementsByTagName('div');
forEach.call(div, function (elem) {
elem.style.backgroundColor = '#f00';
});
var section = document.querySelectorAll('section');
forEach.call(section, function (elem) {
elem.classList.add('fooSection');
});
NodeList
や HTMLCollection
を配列に変換するのも手です。
こうすれば HTMLCollection
も静的なものとして扱えます。
var typeOf = function (obj) {
console.log(Object.prototype.toString.call(obj).slice(8, -1));
};
var div = document.getElementsByTagName('div');
typeOf(div); // "HTMLCollection"
// 配列に変換する
div = Array.prototype.slice.call(div);
typeOf(div); // "Array"
div.forEach(function (elem) {
elem.style.backgroundColor = '#f00';
});
また、ES2015では for...of
を使えます。
const div = document.getElementsByTagName('div');
for (const elem of div) {
elem.style.backgroundColor = '#f00';
}
const section = document.querySelectorAll('section');
for (const elem of section) {
elem.classList.add('fooSection');
}
#DocumentFragment
では使えるメソッドが限られている
DocumentFragment
内にある要素を取得しようとした場合、使えるメソッドは getElementById()
、querySelector()
、querySelectorAll()
のみです。
#おまけ:element.children
と Node.childNodes
の違い
とある要素の子要素を取得する時、element.children
と Node.childNodes
の2つがありますが、これはそれぞれ HTMLCollection
と NodeList
になります。
どちらもリアルタイムで中身が変わる可能性があることには変わりないのですが、Node.childNodes
は余計なノードも含まれるかもしれません。
<div id="foo">Foo <span>Bar!</span></div>
var foo = document.getElementById('foo');
console.log(foo.children); // HTMLCollection (1) [span]
console.log(foo.childNodes); // NodeList (2) ["Foo ", span]
#おわりに
今のブラウザの処理速度を考えると、今後は getElementById()
、querySelector()
、querySelectorAll()
さえ使えればなんとかなりそうですね。