[JS]getElementByIdでは上手くいくのにgetElementsByClassNameでは上手くDOM操作が出来ないとき
posted : 2020.05.10
こんにちは、ma-ya’s CREATE[まーやずくりえいと]です。
先日職場の同僚に相談されたことで、5分くらい考えて「アレ、これ前ハマリかけたやつだわ」とまたもや時間を浪費してしまった僕です。
getElementByIdでは上手くいくのに、getElementsByClassNameとかgetElementsByTagNameでDOM操作が上手くいかない
はい。vanilla JSあるあるではないでしょうか。
ちなみにgetElementsByClassName / getElementsByTagNameには「s」が付きますからね。
getElementsByClassName / getElementsByTagNameですからね。
※getElementByIdに「s」が付いてないのは、同じid名がドキュメント上に存在しないことが前提の為
このスペルミスもあるあるですが今回はそんなことを言いたいわけではございません。
Tips程度の話なので先にサクッと結論から言います。
getElementsByClassName / getElementsByTagName / querySelectorAll の戻り値は配列(のような)オブジェクトだよ。だから要素が一つしかなくても [0] みたいな感じでアクセスしないとDOM操作は出来ないよ。
ですよと。
初めから複数の要素を取得してそれぞれループ処理をする前提だと逆に意識しないかもですが、
getElementsByClassNameで取得するDOM要素が一つだったりすると、jQueryみたいにそのまま処理を施してしまいがち。
そこは落ち着いて[0]のように配列にアクセスする手順を踏んでやればうまくいくわけです。
//「hoge」クラスのついた一つ目の要素を取得 document.getElementsByClassName('hoge')[0]
getElementsByClassName / getElementsByTagNameとquerySelectorAllの戻り値
そしてさらに詳しく見ていくと、
getElementsByClassName / getElementsByTagNameとquerySelectorAllの戻り値は両方とも配列状のオブジェクトとはいえ、似て非なるものです。
- getElementsByClassName / getElementsByTagNameの戻り値:HTMLCollection
- querySelectorAll の戻り値:NodeList
ちょっとサンプルも見てみて下さい。
それぞれのボタンをクリックすると、「TARGET ELEMENT(span要素)」をボタン名のメソッドで取得し、その戻り値が「RETURN VALUE」エリアに反映します。
どうでしょう。
getElementByIdとquerySelectorがHTML要素をそのまま取得できているのに対し、getElementsByClassName / getElementsByTagName / querySelectorAll は「getElementsBy~」の方がHTMLCollection、querySelectorAllはNodeListという配列状オブジェクトです。
[補足]配列「状」オブジェクト
配列「状」とわざわざ書いていますが、文字通りHTMLCollectionとNodeListは配列ではありません。
よって配列を操作するためのメソッドは使えないことも多いので注意。
HTMLCollectionとNodeListの違いをざっくり
じゃあHTMLCollectionとNodeListの違いって何なん??て話ですよね。
細かな挙動の違いは置いといて(知らないので汗)、ここではざっくりとそれぞれの違い・特色に触れておきます。
NodeListはforEachが使える(ただしIE11以外)
IE11以外ですが、NodeListはそのままforEachが使えます。
対して、HTMLCollectionはそのままだとforEachが使えません。
複数の要素を取得する時、多くの場合はループで各要素に処理をすると思うのでこれは由々しき事態です。
ちなみにNodeListにおけるforEachに関する補足はMDNの記載を引用させてもらいます↓
NodeList は Array とは異なりますが、 forEach() メソッドで処理を反復適用することは可能です。 Array.from() を使うことで Array に変換することができます。ただし、古いブラウザーでは NodeList.forEach() も Array.from() も実装されていない場合があります。これらの制限は Array.prototype.forEach() を使うことで回避することが可能です。
NodeListとHTMLCollectionでforEachを使うには、明示的に配列へ変換すればOK
NodeListをIE11でも動くように、かつHTMLCollectionでもforEachする方法は、ひと手間かけてそれぞれの配列状のオブジェクトを明示的に配列へと変換してやればOK。
①いったん変数に配列として格納する方法
const nodeList = document.querySelectorAll('.hoge'); const htmlCollection = document.getElementsByClassName('hoge'); const nodeListArray = Array.prototype.slice.call(nodeList); //NodeListを配列へ変換 const htmlCollectionArray = Array.prototype.slice.call(htmlCollection); //HTMLCollectionを配列へ変換 nodeListArray.forEach(function(item) { //itemに対する処理 }); htmlCollectionArray.forEach(function(item) { //itemに対する処理 });
②Array.prototype.forEach.callを使う方法
const nodeList = document.querySelectorAll('.hoge'); Array.prototype.forEach.call(nodeList, function(item) { //itemに対する処理 }); const htmlCollection = document.getElementsByClassName('hoge'); Array.prototype.forEach.call(htmlCollection, function(item) { //itemに対する処理 });
HTMLCollectionは動的に要素を取得し、NodeListは静的に要素を取得する
要素の取得が動的であるか静的であるか、という違いもあります。
HTMLCollectionが動的であることに対して、NodeListは静的に要素を取得します。
HTMLCollection(getElementsByClassName / getElementsByTagName)の場合
// hogeクラスのついた要素が1つある状態 const hogeNodeList = document.getElementsByClassName('hoge'); console.log(hogeNodeList.length); // => 1 const NewHoge = document.createElement('div'); NewHoge.classList.add('hoge'); document.body.appendChild(NewHoge); console.log(hogeNodeList.length); // => 2(増える=動的)
NodeList(querySelectorAll)の場合
// hogeクラスのついた要素が1つある状態 const hogeNodeList = document.querySelectorAll('.hoge'); console.log(hogeNodeList.length); // => 1 const NewHoge = document.createElement('div'); NewHoge.classList.add('hoge'); document.body.appendChild(NewHoge); console.log(hogeNodeList.length); // => 1(増えない=静的)
よく使う要素取得メソッドの戻り値はどっち?HTMLCollection/NodeListに分類してみた
最後によく使いそうな要素取得メソッドの戻り値を簡単にまとめときましょう。
- getElementById() → HTMLElement
- getElementsByTagName() → HTMLCollection
- getElementsByClassName() → HTMLCollection
- querySelector() → HTMLElement
- querySelectorAll() → NodeList
- element.children → HTMLCollection
- Node.childNodes → NodeList
HTMLElementはもうそのまんま、HTML要素のことです。
単数で取得すること前提のメソッドで要素を取得する場合はこれになるみたい。
また.childrenと.childNodesも戻り値はそれぞれHTMLCollectionとNodeListに分けられるようです。
うーーーーん、深い。
こういうの、普段からゴリゴリJS書いてないと忘れるんだよなあ…