【JavaScript】DOM操作で"getElementsByClassName"の仕組みに苦戦した件

## ▼最初に
JavaScriptで頻繁に使用するDOM操作。操作したい要素を取得して、その要素に対して作成、更新、削除といった操作ができる便利な機能ですよね。
皆さんは要素を取得する時にどのメソッドを使っていますか？私は``getElementById``をよく使っています。と言ってもこれしかほぼ使った事がないのが本音です。
今回は初めて``getElementsByClassName``を使ってDOM操作をする時に苦戦した話をアウトプットとして記事にしたいと思います。

## ▼苦戦した話
まずは``getElementsByClassName``の仕組みに苦戦した話をしたいと思います。
以下のコードを例に説明します。
このプログラムは、ボタン１、２，３が画面上に表示されており、
どのボタンを押しても”ボタンがクリックされました。”と表示されます。

```HTML:HTML
<body>
    <button class="btn">ボタン１</button>
    <button class="btn">ボタン２</button>
    <button class="btn">ボタン３</button>
</body>
```
```JavaScript:JavaScript
// ボタン要素のclassを取得
const btn = document.getElementsByClassName("btn");

//ボタン要素に対してクリックイベント処理
btn.addEventListener('click',function(){
    //pタグを生成
    const p = document.createElement('p');
        //pタグにテキストを追加
        p.textContent = 'ボタンがクリックされました。';
        //body内にpタグを配置
        document.body.appendChild(p);
});
```
▼イメージ図
![名称未設定のデザイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3148670/f9fc8bb9-54e5-7757-7fc8-17fb33d9b865.png)

ところが実際動かしてみると....ボタンを押しても反応がない！！
class名で要素取得して、取得した要素にイベント処理してるから問題ないはずなんだけど...。
とりあえずコンソール見てみることに。
```Console:Console
TypeError: btn.addEventListener is not a function
```
これだけだとよく分からないけど、イベント処理が成立していない事だけは何となく分かった。
デバッグして手前の処理（要素取得処理）を見てみる。
...と変数``btn``の中身見てみたらこんなの出てきました。

![スクリーンショット 2024-07-14 104819.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3148670/f9634b7d-f43c-5196-fcab-ebdfd84aa6a3.png)

**HTMLCollection！？なんだこれ！？**
とりあえずMDNさんに聞いてみる...

>HTMLCollection インターフェイスは、（文書内の順序における）要素の汎用的な集合（arguments のような配列風のオブジェクト）を表し、リストから選択するためのメソッドとプロパティを提供します。
引用：<a href="https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/closePath">MDN</a>

ふむふむ、配列ね...。
変数``btn``の中身は”HTMLCollection”という事が分かりました。
``addEventListener``は一つの要素しか処理できない仕組みで、”HTMLCollection”には複数の要素が含まれていた為にイベント処理が実行されませんでした。

ではどうしたらいいのか。さらに調べるとQiitaに参考となる記事がありました。そちらを手掛かりに修正していきたいと思います。
※記事の最後にリンク貼っておきます。

## ▼修正
修正したコードは以下の通りとなります。

```diff_JavaScript:JavaScript
  const btn = document.getElementsByClassName("btn");
+ const array = Array.prototype.slice.call(btn);

+ array.forEach(function (target) {
+    target.addEventListener('click', function () {    
-    btn.addEventListener('click',function(){
     const p = document.createElement('p');
     p.textContent = 'ボタンがクリックされました。';
     document.body.appendChild(p);
     });
  });
```
それでは順番に解説していきます。

▼１．”HTMLCollection”を配列に変換
一つずつ処理を実行するための準備として、HTMLCollectionから配列に変換します。
※配列に変換する理由は後ほど説明します。

```JavaScript:JavaScript
const array = Array.prototype.slice.call(btn);
```

・配列の一部を取り出して新しい配列にする
``Array.prototype.slice()``

・call()メソッドで変数``btn``を呼び出す
``call(btn)``

▼２．繰り返し処理で全ての要素に対してイベント処理を行う
修正前のコードは、配列（複数要素）に対してイベント処理をしていました。addEventListenerは一つの要素しか処理できない為、繰り返し処理で全ての要素にイベント処理をする修正をしていきます。
ここで使う**forEach()メソッド**は、配列に処理はできますが、HTMLCollectionに処理はできません。その為の準備としてHTMLCollectionを配列に変換しました。※for文は使えます。
```JavaScript:JavaScript
array.forEach(function (target) {
    target.addEventListener('click', function () {
        const p = document.createElement('p');
        p.textContent = 'ボタンがクリックされました。';
        document.body.appendChild(p);
    });
});
```

・forEach文で配列``array``の要素を引数``target``に格納
``array.forEach(function (target)``

・引数``target``に対してイベント処理
``target.addEventListener``

※forEach文の処理

1回目：インデックス番号[0]の要素にイベント処理
2回目：インデックス番号[1]の要素にイベント処理
3回目：インデックス番号[2]の要素にイベント処理

▼３．修正完了
修正完了しました。
全てのボタンに対してイベント処理が実行され、”ボタンがクリックされました。”の文字が表示されるようになりました。

## ▼まとめ
・``getElementsByClassName``で取得できる要素は”HTMLCollection”と呼ばれる集合体。
・``addEventListener``は一つの要素しか処理できない為、forEachメソッドやfor文を使ってすべての要素に対してイベント処理をする。
・”HTMLCollection”にforEachメソッドは使えない為、新しい配列にする必要がある。

私は普段からgetElementByIdで要素を取得していました。今回初めてgetElementsByClassNameを使いましたが、イベント処理が実行できなくてかなり焦りました。しかし、仕組みを理解できたことでもう苦戦することはないと思います。
本記事を通して"getElementsByClassName"の仕組みを理解いただけたら幸いです。

また、エラー対処の流れもお分かりになったのではないでしょうか。
エラーを修正するためにいきなりググっても遠回りになるケースもあります。原因を細分化して一つ一つ丁寧に調べることで原因が分かってきます。
今回は”HTMLCollection”についての解説は割愛したため、モヤモヤしている人もいると思います。こちらに関しては別の記事で解説したいと思いますので、興味のある方は読んでみてください。

## ▼参考記事
<a href="https://qiita.com/_Keitaro_/items/07d3a841726bcc23bffc">【JavaScript】getElementsByClassName() メソッドでハマった件</a>
<a href="https://qiita.com/nagao-koyanagi/items/9b42e8122fab8ad084ce">addEventListenerを使用する際に注意するべきこと</a>