読者の悩み
- 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で要素取得時の注意点は以上になります。
もし実装などで上手くいかないことありましたら、下記ボタンからご相談いただけましたらありがたいです。