【JavaScript】 getElementsByClassNameでforEachがエラーな理由と対処方法
更新日:2021/06/24
JavaScriptでDOM操作をおこなっているとgetElementsByClassNameメソッドで取得した要素をforEachで処理したいときがあります。
しかし実際にコードを作成して実行するとエラーになってしまいます。
ここではその理由と、対処方法についてお伝えします。
エラーの内容
getElementsByClassNameで取得した配列をforEachで処理してみる。
やってしまいがちな間違いコード
const elements = document.getElementsByClassName( "classname" );
elements.forEach( e=>console.log( e ) );
Uncaught TypeError: elements.forEach is not a function
というエラーが出てしまった。
HTMLCollectionはアレイライクなオブジェクト
DOMのgetElementsByClassNameメソッドは、HTMLCollectionというオブジェクトを返します。
このオブジェクトは配列、つまりArrayオブジェクトと混同されることが多いですが、実際はアレイライクなオブジェクトに分類されます。
アレイライクなオブジェクトについては、次のページを読んでみてください。
■【JavaScript】 アレイライク・配列風・配列のようなオブジェクトとは
forEachメソッドを実行するには、オブジェクトにforEachメソッドが定義(実装)されている必要があります。
HTMLCollectionには定義されいないため、先ほどのコードはエラーになってしまったのです。
elements.forEach は undefined と評価されます。
undefinedは関数ではないので、is not a function(関数ではない)というエラーが表示されたのです。
対処法
対処法いくつか挙げてみます。
1:一番素直(おススメ)な対処法
おススメコード
const elements = document.getElementsByClassName( "classname" );
for( let i = 0 ; i < elements.length ; i ++ ) {
console.log( elements[i] );
}
JavaScriptの中級者レベルになると、forループかっこ悪いと感じる人が出てきますが、効率的なこれが一番です。
2:Array.fromを使った対処法
例として挙げられていることが多いけれど、ムダに感じるコードです。
Array.fromを使った対処法
const elements = document.getElementsByClassName( "classname" );
const elementsArray = Array.from( elements ) ;
elementsArray.forEach( e=>console.log( e ) );
Array.fromは、アレイライクなオブジェクトをArrayオブジェクトに変換することも可能なメソッドです。
Array.fromについては、こちらを参考にしてみてください。
■【JavaScript】 Array.fromの使い方を理解する
Arrayオブジェクトに変換後は、forEachメソッドが使用できます。
しかし、forEachメソッドを使用したいという理由だけで、Array.fromを実行するのはムダですね。
3:Array.prototype.forEachを使った対処法
次は僕の中では無理やり感満載なコードです。
Array.prototype.forEachを使った対処法
const elements = document.getElementsByClassName( "aaa" );
Array.prototype.forEach.call( elements , e => console.log( e ) );
ArrayオブジェクトのforEachメソッドの実体は、Array.prototype.forEachです。
この実体に対して、this値を変更するcallメソッドを実行しています。
これにより、HTMLCollectionを配列に見立てて処理できます。
prototypeとcallについては、次のページを読んでみてください。
■【JavaScript】 プロトタイプとは?prototypeプロパティはプロトタイプではない件について
■【JavaScript】 そろそろcall()とapply()を理解してみようと思う
ただしこれは、Array.prototype.forEachがアレイライクなオブジェクトでも動作するように設計されているからこそ可能な方法です。
おそらくないと思いますが、今後のバージョンアップでArrayオブジェクトのみを受け付けるようになったら、動作しなくなります。
この意味からすると、「今はこんな方法もありますよ」というとても消極的な例示にとどめておくべきかもしれません。
4:なにがなんでもgetElementsByClassNameでforEachしたい
getElementsByClassNameで取得した値にそのままforEachしたいときは、次のようにHTMLCollectionにforEachメソッドを組み込みます。
getElementsByClassNameでforEach
HTMLCollection.prototype.forEach = function ( callbackfn , thisArg = undefined ){
for( let i = 0 ; i < this.length ; i ++ )
callbackfn.call( thisArg , this[i] , i , this )
};
const elements = document.getElementsByClassName( "aaa" );
elements.forEach ( e=>console.log( e ) );
forEachで定義している関数は、引数などのチェックをおこなっていません。
いろいろ面倒なので、次のようにArray.prototype.forEachをセットしてしまうのでもいいかもしれません。
HTMLCollection.prototype.forEach = Array.prototype.forEach;
APIも含めて組み込みオブジェクトにプログラムコードでプロパティを追加するのは推奨されていないので、僕としてはおススメしません。
ただ、このような方法もあるという知識は持っている方がいいので、紹介しています。
更新日:2021/06/24
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。