jQueryの$(ʼ#idʼ)や$(ʼ.classʼ)などのDOM取得を生JavaScriptで実装する脱jQuery

jQueryを使わない生のJavaScriptでDOM取得をしてみます。取得だけではなく、注意すべき点がいくつかあるので紹介します。

DOM取得関数

ID名での取得


<ul id="list"></ul>

var id = document.getElementById('list');
// => <ul id="list"></ul>

マッチした1つの要素Elementを返します。マッチしなければ null を返します。

クラス名での取得


<ul>
  <li class="list-item"></li>
  <li class="list-item"></li>
  <li class="list-item"></li>
</ul>

var classes = document.getElementsByClassName('list-item');
// => (3) [li.list-item, li.list-item, li.list-item]

マッチする全ての要素リストHTMLCollectionを返します。マッチしなければ空のHTMLCollectionを返します。

タグ名での取得


<ul>
  <li></li>
  <li></li>
  <li></li>
</ul>

var tags = document.getElementsByTagName('li');
// => (3) [li, li, li]

マッチする全ての要素リストHTMLCollectionを返します。マッチしなければ空のHTMLCollectionを返します。

CSSセレクタでの取得


<ul>
  <li class="list-item">1</li>
  <li class="list-item">2</li>
  <li class="list-item">3</li>
</ul>

var node = document.querySelector('.list-item');
// => <li class="list-item">1</li>

var node = document.querySelector('li');
// => <li class="list-item">1</li>

var nodes = document.querySelectorAll('.list-item');
// => (3) [li.list-item, li.list-item, li.list-item]

querySelector() はマッチする最初の要素のみのElementを返し、マッチしなければ null を返します。querySelectorAll() はマッチする全ての要素リストNodeListを返し、マッチしなければ空のNodeListを返します。

getElementsBy*()querySelectorAll() の違い

HTMLCollectionは動的で、NodeListは静的だというのが大きな違いです。


<ul class="list">
  <li class="list-item">1</li>
  <li class="list-item">2</li>
  <li class="list-item">3</li>
</ul>

var dynamicNode = document.getElementsByClassName('list-item'),
    staticNode = document.querySelectorAll('.list-item');

console.log(dynamicNode.length); // 3
console.log(staticNode.length);  // 3

このようなHTMLがあったとき、両方とも 3 となります。


var node = document.createElement('li');
node.className = 'list-item';
node.textContent = '4';
document.getElementsByClassName('list')[0].appendChild(node);

console.log(dynamicNode.length); // 4
console.log(staticNode.length);  // 3

そのあとに <li class="list-item">4</li> を追加してみます。その後、もう一度 length を確認すると、getElementsBy*()4querySelectorAll()3 と表示されます。動的な方は要素を追加すると更新されるのに対し、静的な方は更新されません。

配列の関数を使うとき

HTMLCollectionNodeListArray-Like Object(配列のようなオブジェクト)といって、配列ではありません。そのため、DOMを取得した後に forEach() で走査したい場合に問題が起こります。


var arrayLikeObject = {0: 'a', 1: 'b', 2: 'c', length: 3};

Array-Like Objectとは length プロパティを持ち、数字の添え字でアクセス可能なオブジェクトのことです。実際には、いくつかのメソッドを持っている場合があります。


Array.prototype.slice.call(document.querySelectorAll('.list-item')).forEach(function(node) {
  console.log(node);
});

Array.prototype.slice.call() でArray-Like Objectを配列に変換します。配列であれば forEach() 関数が使えます。


Array.prototype.forEach.call(document.querySelectorAll('.list-item'), function(node) {
  console.log(node);
});

配列の関数はジェネリックなので直接Array-Like Objectに対して call() することができます。なので、このように書くこともできます。

ES6の Array.from()


Array.from(document.querySelectorAll('.list-item'), function(node) {
  console.log(node);
});

ES6の Array.from() を使えばもっと簡単に書けます。


// Array.from非対応
if (!Array.from) {
  Array.from = function(node, callback) {
    Array.prototype.slice.call(node).forEach(callback);
  };
}

Array.from(document.querySelectorAll('.list-item'), function(node) {
  console.log(node);
});

しかし、IEには対応していないので、簡単なPolyfillを書いてみました。検索すればより強固なPolyfillが見つかります。