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

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

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

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

getElementsByClassNameとHTMLCollectionの違い

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

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など全ての配列メソッドが使えます。

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らしい書き方です。

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に対して直接使えます。配列変換が不要でシンプルです。

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を使う方が便利です。

// 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変更を自動反映)という違いがあります。

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

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

// 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()で配列にコピーしてからループしてください。

4つの方法の比較まとめ

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

よくある質問(FAQ)

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

まとめ

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