JavaScriptで特定のクラスを持つ要素を一括操作するとき、getElementsByClassNameを使おうとして「forEachが使えない」と詰まった経験はありませんか?
原因はgetElementsByClassNameが返すHTMLCollectionが配列ではないからです。本記事では解決策を4つ紹介します。
この記事で学べること:HTMLCollectionとArrayの違い・forEachを使う4つの方法・現代的な書き方(querySelectorAll推奨)
getElementsByClassNameとHTMLCollectionの違い
getElementsByClassName()はHTMLCollectionを返します。HTMLCollectionは配列に似ていますが、配列ではないためforEach・map・filterなどの配列メソッドが使えません。
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のメリット:
breakやcontinueで反復を制御できます。全要素を処理するだけなら最もシンプルな方法です。方法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を変更する場合はライブコレクションに注意