Q1data-id を持つ li が 3 件あるとき、querySelector("[data-id]") が返すのは?
DOM ツリーと要素の取得 — querySelector と querySelectorAll
DOM ツリーから要素を取り出す querySelector と querySelectorAll を扱います。class・id・data 属性で探す書き方、NodeList の配列への変換、見つからないときの ?. を学べます。
商品一覧で、全商品の価格を読みたいとします。#total のように id で探す書き方は id を付けた要素にしか使えず、同じ形の商品が並ぶ一覧に向きません。
この記事では、id 以外の条件で探すセレクタと、すべて集める querySelectorAll、要素が無いときの読み方を扱います。
1 件だけ取り出す — querySelector とセレクタ
お知らせ一覧から、重要なお知らせの見出しだけを読みたいとします。一覧には同じ形の li が並び、どれにも id は付いていません。id はページ内で 1 つの要素にしか付けられない名前なので、「重要なお知らせ」のように種類をまとめる目印には使えません。
セレクタ(探す要素の条件を書いた文字列)では、li ならタグ名、#news なら id、.notice なら class(同じ種類の要素にまとめて付ける名前)で要素を指定します。document.querySelector(セレクタ) は、DOM ツリーの中で条件に合う最初の 1 件を返します。
// お知らせ一覧を用意する(class は空白区切りで複数付けられる)
document.body.innerHTML = `<ul id="news">
<li class="notice">送料の改定</li>
<li class="notice important">臨時メンテナンス</li>
<li class="notice">秋の新商品</li>
</ul>`;
// タグ名で探す/class で探す(. の後ろが class 名)
console.log(document.querySelector("li").textContent); // 送料の改定
console.log(document.querySelector(".notice").textContent); // 送料の改定
console.log(document.querySelector(".important").textContent); // 臨時メンテナンス
"li"と".notice"— どちらもここで探すのをやめる- 文字は「送料の改定」
".important"— ここで探すのをやめる- 文字は「臨時メンテナンス」
- 文字は「秋の新商品」
- 3 つのセレクタは、どれもここまで探しに来ない
".notice" は 1 件目で探すのをやめます。返るのは、最初に当てはまった要素です。2 件目の li には class が 2 つ付いていて、.notice と .important のどちらにも当てはまります。2 件目だけを取りたいときは、.important のように 2 件目にしか当てはまらない条件を書きます。
一覧を全件たどる — querySelectorAll と NodeList
お知らせの見出しを、全件まとめて読みたいとします。querySelector は最初の 1 件で探すのをやめるため、同じセレクタで何度呼んでも「送料の改定」しか返らず、2 件目以降には届きません。
document.querySelectorAll(セレクタ) は、当てはまる要素をすべて NodeList(見つかった要素を DOM ツリーの順に並べた、配列に似たオブジェクト)で返します。length で件数を読め、forEach で 1 件ずつ処理できます。
document.body.innerHTML = `<ul id="news">
<li class="notice">送料の改定</li>
<li class="notice important">臨時メンテナンス</li>
<li class="notice">秋の新商品</li>
</ul>`;
// 当てはまる要素をすべて NodeList で受け取る
const notices = document.querySelectorAll(".notice");
console.log(notices.length); // 3
notices.forEach((notice) => console.log(notice.textContent));
// 送料の改定
// 臨時メンテナンス
// 秋の新商品
// notices.map(...) は TypeError。配列にしてから呼ぶ
const titles = [...notices].map((notice) => notice.textContent);
console.log(titles.join(" / ")); // 送料の改定 / 臨時メンテナンス / 秋の新商品
NodeList のままでも、notices[1] のように番号を指定して 1 件を読めます。スプレッド構文で作った配列は別のオブジェクトなので、変換した後も notices は NodeList のまま残り、続けて forEach に使えます。
番号で 1 件を指す — data 属性と属性セレクタ
お知らせ番号が N-103 の 1 件を取り出したいとします。同じお知らせを別の欄にも並べると、id は重複するので使えません。class は種類をまとめる名前なので、番号の置き場所には向きません。
data 属性(data- で始まる名前の属性)には、番号のような独自の値を自由に持たせられます。属性で探すセレクタは角括弧で囲み、[data-id] は属性を持つ要素、[data-id="N-103"] は値まで一致する要素に当てはまります。
document.body.innerHTML = `<ul id="news">
<li class="notice" data-id="N-101">送料の改定</li>
<li class="notice important" data-id="N-102">臨時メンテナンス</li>
<li class="notice" data-id="N-103">秋の新商品</li>
</ul>`;
// 属性を持つかどうかだけで探す(3 件とも当てはまる)
console.log(document.querySelector("[data-id]").textContent); // 送料の改定
// 値まで一致する要素を探す(値を " で囲むので、セレクタ全体は ' で囲む)
console.log(document.querySelector('[data-id="N-103"]').textContent); // 秋の新商品
値を書かない [data-id] は 3 件すべてに当てはまって 1 件目を返し、値まで書いた [data-id="N-103"] は番号が一致する 1 件だけを指します。下の表は、この記事で使ったセレクタの書き方と、お知らせ一覧で返る要素をまとめたものです。
| セレクタ | 当てはまる要素 | お知らせ一覧で返る要素 |
|---|---|---|
| li | タグ名が li の要素 | 1 件目の li(送料の改定) |
| .important | class に important を含む要素 | 2 件目の li(臨時メンテナンス) |
| #news | id が news の要素 | ul 要素 |
| [data-id] | data-id 属性を持つ要素 | 1 件目の li(送料の改定) |
| [data-id="N-103"] | data-id の値が N-103 と一致する要素 | 3 件目の li(秋の新商品) |
数字で始まる値は引用符が無いと止まる
[data-id=N-103] のように引用符を省いても動く値はありますが、[data-id=102] のような数字で始まる値はセレクタとして読めず、「is not a valid selector」というエラーでその行が止まります。値は、いつも " で囲んで書きます。
要素が見つからなくても止めない — null と ?.
トップページで、緊急のお知らせがあるときだけ見出しを表示したいとします。緊急のお知らせが無い日は要素も無いため、見出しの文字を読む行が TypeError で止まり、その後ろに書いた処理も実行されません。
querySelector は、当てはまる要素が無いと null を返します。取得した値にオプショナルチェーンの ?. を付けて .textContent を読むと、null のときは先を読まずに undefined を返し、?? を続ければ既定値を決められます。
// 緊急のお知らせ(class は urgent)がまだ無い一覧
document.body.innerHTML = `<ul><li class="notice">秋の新商品</li></ul>`;
const urgent = document.querySelector(".urgent");
console.log(urgent); // null
// urgent.textContent は TypeError で止まる
console.log(urgent?.textContent); // undefined
console.log(urgent?.textContent ?? "なし"); // なし
// urgent がある一覧で、. の有無を比べる
document.body.innerHTML = `<ul><li class="urgent">サーバー停止</li></ul>`;
console.log(document.querySelector(".urgent")?.textContent ?? "なし"); // サーバー停止
console.log(document.querySelector("urgent")?.textContent ?? "なし"); // なし
?. を付けるのは、緊急のお知らせのように日によって無いことがある要素だけにします。いつもある要素にまで付けると書き間違いに気づけないので、付けずに TypeError で止め、行番号から原因を探せるようにしておきます。
0 件の NodeList は null ではない
querySelectorAll は当てはまる要素が 1 件も無くても null を返さず、length が 0 の NodeList を返します。forEach を呼んでもエラーにならず何も起きないので、0 件だったことを知りたいときは ?. ではなく length === 0 で確かめます。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2querySelectorAll(".notice") の戻り値に、そのまま map を呼ぶと?
Q3要素が無いとき、querySelector(".sale-price")?.textContent の値は?