【JavaScript】getElementsByClassNameで取得した要素にforEachループ処理を行う方法

JavaScriptで特定のクラスを持つ要素を一括操作するとき、getElementsByClassNameを使おうとして「forEachが使えない」と詰まった経験はありませんか?

原因はgetElementsByClassNameが返すHTMLCollectionが配列ではないからです。本記事では解決策を4つ紹介します。

この記事で学べること:HTMLCollectionとArrayの違い・forEachを使う4つの方法・現代的な書き方(querySelectorAll推奨)
スポンサーリンク

getElementsByClassNameとHTMLCollectionの違い

getElementsByClassName()HTMLCollectionを返します。HTMLCollectionは配列に似ていますが、配列ではないためforEachmapfilterなどの配列メソッドが使えません。

JavaScript:HTMLCollectionはforEachが使えない
const items = document.getElementsByClassName("item");
console.log(items);          // HTMLCollection [li.item, li.item, li.item]
console.log(Array.isArray(items)); // false

// これはエラーになる
items.forEach(el => console.log(el)); // TypeError: items.forEach is not a function
エラーの原因:HTMLCollectionはArray-likeオブジェクト(配列に似ているが配列ではない)です。lengthプロパティと添字アクセスはできますが、forEach・map・filterなどの配列メソッドは持っていません。

方法1:Array.from()で配列に変換する

Array.from()でHTMLCollectionを本物の配列に変換すれば、forEachなど全ての配列メソッドが使えます。

JavaScript:Array.from()で配列に変換
const items = document.getElementsByClassName("item");

// 配列に変換してforEach
Array.from(items).forEach(function(el) {
    el.style.color = "blue";
});

// アロー関数で簡潔に
Array.from(items).forEach(el => el.classList.add("active"));

// mapやfilterも使える
const texts = Array.from(items).map(el => el.textContent);
console.log(texts); // ["テキスト1", "テキスト2", ...]
Array.from()のメリット:可読性が高く、2番目の引数にマップ関数も渡せます(Array.from(items, el => el.textContent))。

方法2:スプレッド構文で配列化する

スプレッド構文(...)を使っても配列に変換できます。モダンなJavaScriptらしい書き方です。

JavaScript:スプレッド構文で配列化
const items = document.getElementsByClassName("item");

[...items].forEach(el => {
    console.log(el.textContent);
    el.style.backgroundColor = "#f0f0f0";
});

// 配列メソッドのチェーン
[...items]
    .filter(el => el.dataset.active === "true")
    .map(el => el.textContent)
    .forEach(text => console.log(text));

方法3:for…ofループで直接反復する

for...ofループはHTMLCollectionに対して直接使えます。配列変換が不要でシンプルです。

JavaScript:for…ofで反復
const items = document.getElementsByClassName("item");

for (const el of items) {
    el.textContent = "更新済み";
    el.setAttribute("data-processed", "true");
}
for…ofのメリット:breakcontinueで反復を制御できます。全要素を処理するだけなら最もシンプルな方法です。

方法4:querySelectorAllを使う(推奨)

実はquerySelectorAll()はNodeListを返し、NodeListにはforEachが直接使えます。現代的な開発ではquerySelectorAllを使う方が便利です。

JavaScript:querySelectorAll(forEach直接OK)
// querySelectorAllはforEachが直接使える
const items = document.querySelectorAll(".item");

items.forEach(el => {
    el.style.color = "red";
});

// 複数クラスや属性の組み合わせも可能
document.querySelectorAll(".item.active[data-type='link']")
    .forEach(el => console.log(el.href));
querySelectorAll vs getElementsByClassName:querySelectorAllはCSSセレクタ全体に対応しており、NodeListのforEachが使えて便利です。一方、getElementsByClassNameはライブコレクション(DOM変更を自動反映)という違いがあります。

querySelector / querySelectorAll の使い方はquerySelectorの使い方、取得した複数要素の操作(forEach・配列変換・filter)はquerySelectorAllで取得した複数要素を操作する方法、配列化した後の map / filter / reducemap・filter・reduceの使い方を参考にしてください。

静的コレクション vs ライブコレクション

HTMLCollectionとNodeListには重要な違いがあります。

JavaScript:ライブ vs 静的コレクション
// getElementsByClassName → ライブコレクション(DOM変更を自動反映)
const liveList = document.getElementsByClassName("item");
console.log(liveList.length); // 3
document.querySelector(".item").remove();
console.log(liveList.length); // 2 ← 自動で変わる!

// querySelectorAll → 静的スナップショット
const staticList = document.querySelectorAll(".item");
console.log(staticList.length); // 3
document.querySelector(".item").remove();
console.log(staticList.length); // 3 ← 変わらない
ループ中にDOM削除する場合の注意:ライブコレクション(HTMLCollection)をループ中に削除・追加すると長さが変わってスキップが発生します。この場合はArray.from()で配列にコピーしてからループしてください。

動くデモ:ライブ vs 静的コレクション

↓「項目を追加」を押すと、HTMLCollection(ライブ)は件数が自動で増え、NodeList(静的)は変わりません:

  • 項目1
  • 項目2
  • 項目3

HTMLCollection(ライブ).length = 3
NodeList(静的).length = 3

4つの方法の比較まとめ

方法 forEach使用 複数クラス ライブ反映 推奨度
Array.from(collection) ×
[…collection] ×
for…of
querySelectorAll × ◎(最推奨)

実践:すべての要素にイベントを設定する

実務でよくあるのは「該当するすべての要素にクリックイベントを設定する」場面です。querySelectorAll なら forEach が直接使えて簡潔です。

JavaScript:全要素にクリックイベント(querySelectorAll推奨)
document.querySelectorAll(".item").forEach((el) => {
  el.addEventListener("click", () => {
    el.classList.toggle("selected"); // クリックで選択状態を切り替え
  });
});

getElementsByClassName から始める場合は、[...](スプレッド)で配列化してから forEach します。

JavaScript:getElementsByClassNameから始める場合
[...document.getElementsByClassName("item")].forEach((el) => {
  el.addEventListener("click", () => el.classList.toggle("selected"));
});
パフォーマンスはどれを選んでもOK:要素が数百程度なら、4つの手法の速度差は体感できないレベルです。可読性で選んで問題ありません。巨大なコレクションでも、一度だけ配列化すれば実用上のコストはほぼ無視できます。判断に迷ったら、CSSセレクタが使えて forEach も直接呼べるquerySelectorAll を既定にするとよいでしょう。

よくある質問(FAQ)

QquerySelectorAllのNodeListとArrayの違いは何ですか?
ANodeListはforEach・keys・values・entriesが使えますが、map・filter・reduceは使えません。これらも使いたい場合はArray.from(nodeList)または[...nodeList]で配列に変換してください。
QgetElementsByClassNameが古いブラウザでも動作するのか心配です
AgetElementsByClassNameはIE9以降を含む全モダンブラウザで動作します。querySelectorAllも同様です。Array.from・スプレッド構文はIE11非対応ですが、現在ではIEを考慮する必要はほぼありません。
QHTMLCollectionをfor…inでループできますか?
Aやめてください。for…inはHTMLCollectionのプロトタイプのプロパティも列挙してしまい、DOM要素以外の値が混入する可能性があります。for…of・Array.from・querySelectorAllを使ってください。

まとめ

  • getElementsByClassNameはHTMLCollectionを返すためforEachが直接使えない
  • Array.from()またはスプレッド構文で配列化するとforEachが使える
  • for…ofはHTMLCollectionに直接使えてシンプル
  • 現代的な書き方ではquerySelectorAllを使うのが最もスマート
  • ループ中にDOMを変更する場合はライブコレクションに注意