要素の取得方法まとめ

#はじめに

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を持つ要素が複数存在する場合は最初の要素しか取得しません。

```html:HTML
<span id="spanText">Text</span>
```

```js:JavaScript
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の操作をした場合に中身が変わる可能性があります。

```html:HTML
<ul id="textList">
  <li>Text 1</li>
  <li>Text 2</li>
  <li>Text 3</li>
</ul>
<ul>
  <li id="foo">Text 4</li>
</ul>
```

```js:JavaScript
/* 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の操作をした場合に中身が変わる可能性があります。

```html:HTML
<ul id="textList">
  <li>Text 1</li>
  <li class="foo">Text 2</li>
  <li>Text 3</li>
</ul>
<p class="foo">Text 4</p>
```

```js:JavaScript
/* 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においてはほとんど活躍することはないと思います。

```html:HTML
<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>
```

```js:JavaScript
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のセレクタの記述もブラウザが対応していれば可能です。

```html:HTML
<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>
```

```js:JavaScript
/* 最初の .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')` に近いメソッドです。

```html:HTML
<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>
```

```js:JavaScript
/* .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` を使うのも手です。

```js:JavaScript
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` も静的なものとして扱えます。

```js:JavaScript
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` を使えます。

```js:JavaScript
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` は余計なノードも含まれるかもしれません。

```html:HTML
<div id="foo">Foo <span>Bar!</span></div>
```

```js:JavaScript
var foo = document.getElementById('foo');

console.log(foo.children);    // HTMLCollection (1) [span]
console.log(foo.childNodes);  // NodeList (2) ["Foo ", span]
```

#おわりに

今のブラウザの処理速度を考えると、今後は `getElementById()`、`querySelector()`、`querySelectorAll()` さえ使えればなんとかなりそうですね。