HTMLCollectionとNodeListの違い

JavaScriptロゴ

JavaScriptのHTMLCollectionとNodeListは、HTML文書内の要素を表すために使用される配列のようなオブジェクトです。
「配列のような」というのは、どちらもlengthプロパティを持ち、インデックス番号(添字)でアクセスできるためです。

しかし実際には配列ではなく、また、HTMLCollectionとNodeListはそれぞれ似て非なる型のオブジェクトであるため、JavaScriptで扱う際には注意が必要です。

複数のHTML要素を取得する方法

JavaScriptでタグ名やクラス名などを指定して、複数の要素を取得する方法はいくつかあります。

その1 「getElementsBy」でHTML要素を取得

JavaScriptで複数のHTML要素を取得するには、下記の方法がよく使われます。
これは太古の昔からある方法です。
かつてはIE6に対応するために、これらの方法がよく使われていました。

document.getElementsByClassName()
document.getElementsByTagName()
document.getElementsByName()

ここで返されるのが HTML Collection というものです。
ブラウザの開発コンソールで以下のように試すと HTMLCollection と表示されます。

document.getElementsByTagName('div')
▶ HTMLCollection(3) [div, div, div]

以下のように配列と同様にインデックス番号(添字)で取り出すことができます。

const elms = document.getElementsByTagName('div');
console.log(elms[0]);
▶ <div>...</div>

その2 「querySelectorAll」でHTML要素を取得

複数のHTML要素を取得する別の方法として querySelectorAll があります。
getElementsBy…よりも少し新しい方法です。といっても2008年頃からあります。
(参考) IE8 で実装された Selectors API とは何か? – IT戦記

これはCSSセレクタと同じ書き方で要素を取得できて便利です。

document.querySelectorAll('div')
▶ NodeList(3) [div, div, div]

getElementsByで取得した時と違って NodeList と表示されています
こちらもインデックス番号(添字)でアクセスできるのは同じです。

const elms = document.querySelectorAll('div');
console.log(elms[0]);
▶ <div>...</div>

getElementsByとquerySelectorAllの使い分け

querySelectorAllの方が便利で使いやすいですが、速度面ではgetElementsByの方が速いので、特に大量に要素がある場合などは、要素の指定が単純なもの(クラス名やタグ名など)はgetElementsByで取得して、指定が複雑なものはquerySelectorAllで取得するのが良いと思います。

HTMLCollectionとNodeListの違い

では HTMLCollectionとNodeListの違いは何でしょうか?大きく2つの違いがあります。

  • 要素が増減した時の挙動
  • メソッドの違い

要素が増減した時の挙動

HTMLCollectionは後から要素の数が増減すると、動的に反映されます。
ライブオブジェクトと呼ばれる性質のオブジェクトです。

NodeListは要素の数が変わっても、後からそれが反映されません。

※より正確には「querySelectorAllで取得したNodeList」はライブオブジェクトではありません。
Node.childNodesでもNodeListを取得できますが、そちらはライブオブジェクトになります。

メソッドの違い

ブラウザの開発コンソールでNodeListのprototypeを展開するとわかりますが、NodeListにはforEachメソッドがありますが、HTML Collectionにはありません。

document.querySelectorAll('div')
▼ NodeList [main]
	▶0: div
	length: 1
	▼ [[Prototype]]: NodeList
		▶entries: ƒ entries()
		▶forEach: ƒ forEach()
		▶item: ƒ item()
		▶keys: ƒ keys()
		 length: (...)
		▶values: ƒ values()
		▶constructor: ƒ NodeList()
		▶Symbol(Symbol.iterator): ƒ values()
		 Symbol(Symbol.toStringTag): "NodeList"
		▶get length: ƒ length()
		▶[[Prototype]]: Object

※以前はNodeListにもforEachメソッドがありませんでしたが、Chrome 51(2016年)で追加されたようです (参考) NodeList.prototype.forEach() – Web APIs | MDN

HTMLCollectionを配列のようにループさせるには?

以下はlengthプロパティを使った昔ながらのforループです。昔からある定石のような方法です。

const elms = document.getElementsByTagName('div'); // HTMLCollection
const len = elms.length;
for (let i=0; i<len; i++) {
  console.log(elms[i]);
}

ちなみにlengthをあらかじめ変数に代入しているのは、ほぼ気持ちの問題ですがループごとに要素のlengthプロパティにアクセスするよりも速そうだからです。

ES6(ECMAScript 2015)以降であれば(IE を対象外にすれば)、for of 文を使って簡潔に記述することもできます。※for inではなくfor ofであることに注意してください

const elms = document.getElementsByTagName('div'); // HTMLCollection
for (const e of elms) {
  console.log(e);
}

for文の時は let i=0 だったのに、for of の時は constを使っていることに疑問を感じたかもしれません。これは for in や for of ではループごとにスコープが変わるためです。毎回違うブロックで実行されているイメージです。

HTML Collectionも配列に変換することで、配列と同じforEachやmapやfilterなどのメソッドが使えるようになります。

Array.from で配列に変換してmapでループ

const elms = document.getElementsByTagName('div'); // HTML Collection
const elmsAry = Array.from(elms); // 配列に変換
elmsAry.map( (v) => { console.log(v); } ); // 配列なのでmapでループできる

※Array.fromはIEは非対応です

スプレッド構文[…array]で配列に変換してmapでループ

const elms = document.getElementsByTagName('div'); // HTML Collection
const elmsAry = [...elms]; // 配列に変換
elmsAry.map( (v) => { console.log(v); } ); // 配列なのでmapでループできる

配列と似ているので、ついうっかりmapを使おうとして「ん?」となりがちなHTMLCollectionとNodeListですが、しっかり違いを理解して、楽しく要素をループさせましょう。

コメント

タイトルとURLをコピーしました