こんにちは〜 はてなエンジニア - Qiita Advent Calendar 2024 - Qiita の 1日目です。
querySelectorAllの結果をmapしたいとき
JavaScript で、 Document: querySelectorAll() メソッド の結果を map したいことがある。が、そのままだと map できない。
Chrome の devtools で試すとこう↓。 map は関数ではないです、というエラーになっている。
document.querySelectorAll('div').map(div => div.innerText) // Uncaught TypeError: document.querySelectorAll(...).map is not a function // at <anonymous>:1:34
map するには、 Array.from() を使って、 Array インスタンスにしてあげると良い。
Array.from(document.querySelectorAll('div')).map(div => div.innerText) //=> ['hoge', ...] (innerText が入った配列が返ってくる)
くわしく
querySelectorAll の戻り値は NodeList というオブジェクト。
https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll
Document
のquerySelectorAll()
メソッドは、指定された CSS セレクターに一致する文書中の要素のリストを示す静的な(生きていない)NodeList
を返します。
NodeList に map メソッドが存在しないので、そのまま map しようとすると冒頭のエラーになる。 MDN の NodeList のページには、まさに Array.from を使うといい、と書かれている。
メモ:
https://developer.mozilla.org/ja/docs/Web/API/NodeListNodeList
はArray
とは異なりますが、forEach()
メソッドで処理を反復適用することは可能です。Array.from()
を使うことでArray
に変換することができます。
ちなみに"静的な(生きていない)NodeList"とは
生きた NodeList と生きていない NodeList というのは、 DOM の変化に応じて、自動的に更新されるかどうか、という話題。 NodeList - Web API | MDN に詳しい。
querySelectorAll の戻り値は生きていないほうなので、取得して変数に格納したオブジェクトの内容が変化しない。逆に、 Node: childNodes プロパティ は生きた NodeList なので、 DOM が変化すると、その内容も変わりうる。
試しに、 body タグの childNodes を変数に格納してから、 body タグに div タグをひとつ増やしてみた。変数に格納しておいたオブジェクトが変化していることがわかる。
child = document.body.childNodes //=> NodeList(5) [script, div#root, script#hydration, div#a11y-status-message, div.ReactModalPortal] document.body.insertAdjacentHTML('beforeend', '<div>hello!</div>') //=> undefined child //=> NodeList(6) [script, div#root, script#hydration, div#a11y-status-message, div.ReactModalPortal, div]
querySelectorAll に似たメソッド群で Document: getElementsByClassName() メソッド などは、生きた HTMLCollection が戻り値になっている。 querySelectorAll と同じ気持ちでコードを書いているとハマることがありそう……。
list = document.getElementsByClassName('browsers') //=> HTMLCollection [div.browsers] document.body.insertAdjacentHTML('beforeend', '<div class="browsers">Hi!</div>') //=> undefined list //=> HTMLCollection(2) [div.browsers, div.browsers]