順番に読み進めながら学べます

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);   // 臨時メンテナンス
セレクタごとに見つかる要素
body 要素 — 上から順に探す
ul 要素 — id は news
1 件目の li — class は notice
  • "li"".notice" — どちらもここで探すのをやめる
  • 文字は「送料の改定」
2 件目の li — class は notice と important
  • ".important" — ここで探すのをやめる
  • 文字は「臨時メンテナンス」
3 件目の li — class は notice
  • 文字は「秋の新商品」
  • 3 つのセレクタは、どれもここまで探しに来ない
notice の li は 3 件ありますが、".notice" は 1 件目で探すのをやめます。返るのは、最初に当てはまった要素です

2 件目の li には class が 2 つ付いていて、.notice.important のどちらにも当てはまります。2 件目だけを取りたいときは、.important のように 2 件目にしか当てはまらない条件を書きます。

商品一覧のページから、タグ名・class・id を使い分けて要素を 1 件ずつ取り出します。HTML は先頭の document.body.innerHTML への代入で宣言済みです。

① タグ名で最初の商品を探し、その文字を表示してください。

② おすすめの商品にだけ付いた class で探し、その文字を表示してください。

③ 件数を表示している要素を id で探し、その文字を表示してください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

一覧を全件たどる — 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(" / "));   // 送料の改定 / 臨時メンテナンス / 秋の新商品
map は配列にしてから呼ぶ
notices.map(...)NodeList にmap は無いTypeError でその行が止まるtitles は作られない[...notices].map(...)同じ 3 件を並べた配列配列の map が1 件ずつ呼ばれる["送料の改定", …]の 3 件
NodeList のまま map を呼ぶと止まり、配列にすると動きます。map や filter は、配列にしてから呼びます

NodeList のままでも、notices[1] のように番号を指定して 1 件を読めます。スプレッド構文で作った配列は別のオブジェクトなので、変換した後も notices は NodeList のまま残り、続けて forEach に使えます。

購入明細のページから、買った商品を全件まとめて読み取り、件数と 2000 円以上の商品の数を確かめます。明細の HTML は先頭の document.body.innerHTML への代入で宣言済みです。

① 明細の商品の要素をすべて取得し、件数を表示してください。

② 取得した要素を 1 件ずつ取り出し、商品の文字を順に表示してください。

③ 価格の要素をすべて取得して配列に変換し、2000 円以上だけに絞り込んで件数を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

番号で 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="N-103"[data-id="N-103"]'[data-id="N-103"]'li に書いた属性名前="値" の形角括弧で囲むとセレクタになる中に " があるので外側は ' で囲む
HTML の属性を角括弧で囲むとセレクタになり、JS に渡すときは外側を ' で囲みます。値の " と外側の ' は、別々の役割です

値を書かない [data-id] は 3 件すべてに当てはまって 1 件目を返し、値まで書いた [data-id="N-103"] は番号が一致する 1 件だけを指します。下の表は、この記事で使ったセレクタの書き方と、お知らせ一覧で返る要素をまとめたものです。

セレクタ当てはまる要素お知らせ一覧で返る要素
liタグ名が li の要素1 件目の li(送料の改定)
.importantclass に important を含む要素2 件目の li(臨時メンテナンス)
#newsid が 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」というエラーでその行が止まります。値は、いつも " で囲んで書きます。

文具店の在庫一覧で、商品番号やセール対象の印を手がかりに、目的の商品を取り出します。在庫一覧の HTML は先頭の document.body.innerHTML への代入で宣言済みです。

① 商品番号が P-203 の要素を探し、その文字を表示してください。

② セール対象の属性を持つ要素を探し、その文字を表示してください。

③ 商品番号の属性を持つ要素をすべて取得し、件数を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

要素が見つからなくても止めない — 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 ?? "なし");    // なし
3 通りの探し方と表示される文字
querySelector(…)?.textContent ?? "なし"urgent が無い一覧".urgent" で探すurgent がある一覧".urgent" で探すurgent がある一覧"urgent" で探すnull が返り?. が先を読まないli 要素が返り文字を読むタグ名 urgent で探しnull が返る「なし」と表示「サーバー停止」と表示あるのに「なし」と表示
1 列目と 3 列目は、どちらも null から「なし」になります。?. は、要素が無いこととセレクタの書き間違いを区別しません

?. を付けるのは、緊急のお知らせのように日によって無いことがある要素だけにします。いつもある要素にまで付けると書き間違いに気づけないので、付けずに TypeError で止め、行番号から原因を探せるようにしておきます。

0 件の NodeList は null ではない

querySelectorAll は当てはまる要素が 1 件も無くても null を返さず、length が 0 の NodeList を返します。forEach を呼んでもエラーにならず何も起きないので、0 件だったことを知りたいときは ?. ではなく length === 0 で確かめます。

商品詳細ページで、無いことがあるレビューとセール価格を読み取ります。商品詳細の HTML は先頭の document.body.innerHTML への代入で宣言済みです。

① 商品名の文字を表示してください。

② レビューの li をすべて取得し、0 件なら「レビューはまだありません」と表示してください。

③ セール価格の文字を、要素が無くても止まらない書き方で表示してください。

④ セール価格が無いときは、通常価格の文字を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1data-id を持つ li が 3 件あるとき、querySelector("[data-id]") が返すのは?

Q2querySelectorAll(".notice") の戻り値に、そのまま map を呼ぶと?

Q3要素が無いとき、querySelector(".sale-price")?.textContent の値は?