【よくわからないシリーズ】JavaScriptで要素取得の注意点

読者の悩み

  • JavaScriptで要素取得の方法がたくさんあるのはなぜ?
  • 要素をうまく利用できないことがあるのはなぜ?
  • 確実に要素を処理する方法を教えてほしい。

取得した要素を処理しようとするとなぜかエラーが出るんだけど、理由がわからないので教えてほしい...

僕もこれを理解するのにけっこう時間がかかったので、なぜ、取得方法によってエラーが出たりするのかを解説したいと思います。

本記事の内容

  • JavaScriptの要素取得方法での結果の違いとは
  • 要素取得の取得結果によって処理方法が違う
  • 処理でエラーが出るなら配列に変換しよう

JavaScriptの要素取得方法での結果の違いとは

JavaScriptで要素を取得して、処理しようとするとエラーになったりする理由は要素の取得結果が、取得方法により違うからです。

要素を取得した結果は3種類あります。

  • HTMLElement(HTMLそのもの)
  • NodeList
  • HTMLCollection

ちょっと、JavaScriptを触ったことがある人ならNodeListやHTMLCollectionはみたことがあるのではないでしょうか?

そう!これらの表示は要素を取得して、console.logでデバックした時に表示されます。

これらはどういう意味かというと、取得メソッドによって取得型式が違うことを表しています。

まずは、取得型式がどういうメソッドで取得できるのかをみていきます。

HTMLElement(HTMLそのもの)

  • getElementById()
  • querySelector()

基本的に一つの要素だけを取得するメソッドが、HTMLElement(HTMLそのもの)を所得できます。コンソールでHTMLがそのまま取得できる時がありますよね?それがHTMLElementです。

例をみてみましょう。

例えば、こんなHTMLを取得する時にquerySelectorを使うとしましょう。

<body>
  <h1 class="h1-block">TONOBLOG</h1>
  
  <script>
    const h1 = document.querySelector('.h1-block');
    console.log(h1);
  </script>
 
</body>

コンソールではこのように表示されます。

HTMLそのものを取得できてますね。

このHTMLElementはその後の処理も問題なく実行できます。問題は次からの処理結果になります。

NodeList

  • querySelectorAll()
  • getElementsByName()
  • childNodes

NodeListは配列に似たようなものであり、配列では無いです。
ですが、配列のように扱うことが可能です。

例えば、このようなHTMLがあったとします。

<body>
  <ul class="ul-block">
    <li class="ul-block__item">menu1</li>
    <li class="ul-block__item">menu2</li>
    <li class="ul-block__item">menu3</li>
  </ul>
  
  <script>
    const ulChild = document.querySelectorAll('.ul-block__item');
    console.log(ulChild);
  </script>
</body>

今回はquerySelectorAllを使いました。
処理結果はこちら。

NodeListで取得できてますね。
取得数もちゃんと3つありそうです。

このNodeListは表示がめっちゃ配列っぽいですよね。僕も完全に配列だと思っていました。

NodeListは配列ではないですが配列っぽく扱えます。

console.log(ulChild[0]);を追記します。

<script>
   const ulChild = document.querySelectorAll('.ul-block__item');
   console.log(ulChild);
   console.log(ulChild[0]);
</script>

処理結果はこちら。

普通に配列のように書いて一つの要素を取り出せましたね。完全に配列のようですが配列ではないです。
JavaScript初学者は完全に配列だと思ってしまいますよね。

リファレンスにも配列ではないと書いてあります。

HTMLCollection

  • getElementsByTagName()
  • getElementsByClassName()
  • children

HTMLCollectionも配列に似たようなものであり、配列では無いです。
HTMLCollectionも配列っぽく扱えます。

getElementsByClassName()を使っていきたいと思います。

<body>
  <ul class="ul-block">
    <li class="ul-block__item">menu1</li>
    <li class="ul-block__item">menu2</li>
    <li class="ul-block__item">menu3</li>
  </ul>
  
  <script>
    const ulChild = document.getElementsByClassName('ul-block__item');
    console.log(ulChild);
    console.log(ulChild[0]);
  </script>
</body>

getElementsByClassName()で取得しています。
コンソールで取得内容をみていきましょう。

HTMLCollectionで取得できました。取得内容も3つ取得できているようです。

ulChild[0]で配列と同じように、1つの要素も取り出すことに成功しています。
HTMLCollectionも配列に似たような感じですが、配列ではないです。

NodeListとHTMLCollectionは全く同じような動きをしていますが、この違いはあるのでしょうか?

NodeListとHTMLCollectionの違いとは?

  • NodeListは静的なHTMLを取得します
  • HTMLCollectionは動的なHTMLを取得します

動的か静的かの微妙な違いになります。
静的なHTMLと動的なHTMLとは、どういう意味でしょうか?

静的なHTMLとはHTMLファイルに書かれているままのいじられてないHTMLのことです。
動的なHTMLとはJavaScriptなどで後でHTMLを変更したHTMLのことです。

例えば、このようなHTMLがあったとします。

<body>
  <ul class="ul-block">
    <li class="ul-block__item">menu1</li>
    <li class="ul-block__item">menu2</li>
    <li class="ul-block__item">menu3</li>
  </ul>
  
  <script>
    const ulChildNodeList = document.querySelectorAll('.ul-block__item');
    const ulChildHtmlCollection = document.getElementsByClassName('ul-block__item');
    // 始めに表示する
    console.log(`NodeList: ${ulChildNodeList.length}`);
    console.log(`HTMLCollection: ${ulChildHtmlCollection.length}`);

    // li要素を作って、ul-blockの中に追加。
    const li = document.createElement('li');
    li.classList.add('ul-block__item');
    li.textContent = 'menu4';
   //ul-blockの子要素にliを追加する
    document.querySelector('.ul-block').appendChild(li);

   // もう一度表示
    console.log(`NodeList: ${ulChildNodeList.length}`);
    console.log(`HTMLCollection: ${ulChildHtmlCollection.length}`);
  </script>
</body>

まずはじめに、ul-blockの子要素をquerySelectorAllでNodeListで表示します。そして、getElementsByClassNameでHTMLCollectionで表示します。

今回はわかりやすく、後ろに「.length」を付けて要素の数を取得しました。

次はJavaScriptでli要素を作り、ul-blockの子要素として追加します。

もう一度、NodeListと、HTMLCollectionで取得します。
コンソールをみてみましょう。テンプレートリテラルでわかりやすく表示しています。

HTMLCollectionのはじめに表示した要素数は3で、li要を追加後は、4になっています。動的にHTML要素を取得できました。

NodeListは始めも終わりも要素数が3のままです。こちらは静的にHTMLを取得していますね。

これがHTMLCollectionが動的に更新されているのに対して、NodeListは静的で、動的に要素が増えたとしても、更新しないようになっています。

次はHTMLCollectionとNodeListの利用方法をみていきます。

要素取得の取得結果によって処理方法が違う

HTMLCollectionもNodeListも複数の要素を取得するための形式です。

なので、取得したら繰り返し処理で一つずつ取り出して、処理していくことになります。

繰り返しといえば、for文と、forEachですよね。

実は、HTMLCollectionは「forEach」を使うとエラーになってしまいます。

例えば、このようなHTMLがあったとします。

<body>
  <ul class="ul-block">
    <li class="ul-block__item">menu1</li>
    <li class="ul-block__item">menu2</li>
    <li class="ul-block__item">menu3</li>
  </ul>
  
  <script>
    const ulChildNodeLists = document.querySelectorAll('.ul-block__item');
    const ulChildHtmlCollections = document.getElementsByClassName('ul-block__item');
    // NodeListをforEachで処理する
    ulChildNodeLists.forEach(ulchildNodeList => {
      console.log(ulchildNodeList.textContent);
    })
   // HTMLCollectionをforEachで処理する
    ulChildHtmlCollections.forEach(ulChildHtmlCollection => {
      console.log(ulChildHtmlCollection.textContent);
    })

  </script>
</body>

NodeListで所得した要素と、HTMLCollectionで所得した要素をそれぞれforEach構文で表示させます。

今回はわかりやすいように、textContantを使い、テキストのみを取得します。

コンソールで表示すると、

NodeListの方は取得できましたが、HTMLCollectionの方は、forEachが使えない。というエラーを吐いてます。

HTMLCollectionはforEachは使えないということになります。

それでは、次はlengthを使ったfor文で処理してみます。

  <script>
    const ulChildNodeLists = document.querySelectorAll('.ul-block__item');
    const ulChildHtmlCollections = document.getElementsByClassName('ul-block__item');
    // NodeListをfor文で処理する
    for (let i = 0; i < ulChildNodeLists.length; i++) {
      console.log(ulChildNodeLists[i].textContent);
    }
    // HTMLCollectionをfor文で処理する
    for (let i = 0; i < ulChildHtmlCollections.length; i++) {
      console.log(ulChildHtmlCollections[i].textContent);
    }
  </script>

上記内容をコンソールで表示してみます。

lengthを使ったfor文はHTMLCollectionも取り出すことができましたね。

それでは、次はfor文の書き方の一つで、for/in文で取得してみます。

<body>
  <ul class="ul-block">
    <li class="ul-block__item">menu1</li>
    <li class="ul-block__item">menu2</li>
    <li class="ul-block__item">menu3</li>
  </ul>
  
  <script>
    const ulChildNodeLists = document.querySelectorAll('.ul-block__item');
    const ulChildHtmlCollections = document.getElementsByClassName('ul-block__item');

    for (let i in ulChildNodeLists) {
      console.log(ulChildNodeLists[i]);
    }

    for (let i in ulChildHtmlCollections) {
      console.log(ulChildHtmlCollections[i]);
    }
  </script>
</body>

上記内容をコンソールで表示します。

NodeListもHTMLCollectionも、余計なものをいっぱい引っ張ってきてしまいました。

もし、for文でNodeListやHTMLCollectionから要素を取り出すなら、lengthを使った書き方で取得しましょう。

もし、forEachしか使いたくないんだ!という人がいるなら次の方法があります。

エラーが出るなら配列に変換しよう

HTMLCollectionは配列に変換すれば、forEachを使うことができます。

次のHTMLをみてみましょう。

<body>
  <ul class="ul-block">
    <li class="ul-block__item">menu1</li>
    <li class="ul-block__item">menu2</li>
    <li class="ul-block__item">menu3</li>
  </ul>
  
  <script>
    const ulChildNodeLists = document.querySelectorAll('.ul-block__item');
    const ulChildHtmlCollections = document.getElementsByClassName('ul-block__item');
    console.log(ulChildHtmlCollections);

    // HTMLCollectionを配列に変換
    const arrayUlChildHtmlCollections = Array.prototype.slice.call(ulChildHtmlCollections);
    console.log(arrayUlChildHtmlCollections);

    // 変換した配列をforEachで処理
    arrayUlChildHtmlCollections.forEach(arrayUlChildHtmlCollection => {
      console.log(arrayUlChildHtmlCollection);
    });
    
  </script>
</body>

Array.prototype.slice.call()で配列に変換できます。

Array.prototype.slice.call()で配列に変換して、その変換した配列で、forEachで処理して要素を取り出しています。

コンソールで確認してみましょう。

1行目がHTMLCollection、
2行目がHTMLCollectionを配列に変換した値、
3、4、5行目がforEachで一つずつ要素を取り出してます。

HTMLCollectionを配列にすることで、forEachが使えましたね。

もう一つ注意点です。
配列で使えるメソッドはNodeListやHTMLCollectionの状態では、使えないです。

配列のメソッドとは、

  • map
  • indexOf
  • filter

など、他にもたくさんの配列用のメソッドがあります。

例えば、mapやindexOfなど、配列に使う用のメソッドはNodeListやHTMLCollectionの状態では使えないので、配列に事前に変換しておきましょう。

配列にしておけば、要素の取り出しや配列のメソッドも問題なく使えるので、困ったら配列に変換することは覚えておきましょう。

今回のJavaScriptで要素取得時の注意点は以上になります。

もし実装などで上手くいかないことありましたら、下記ボタンからご相談いただけましたらありがたいです。

この記事を書いた人

アバター

トノムラマサシ

masashi
Webサイト屋兼ブロガー|過去に企業常駐などを経て現在はフリーランスでディレクションとコーダーとして活動しております。JS大好きなのでJSの仕事依頼お待ちしております。京都在中で5人家族。