querySelectorとは、CSSセレクタに一致する最初の要素を1つ返すメソッドです。 一致する要素がなければ null を返します。すべての要素がほしいときは querySelectorAll を使い、こちらは一致した要素をまとめた NodeList(要素の一覧)を返します。

この記事では、セレクタの書き方24例を「実際に何が取れたか」つきの早見表にまとめました。あわせて、querySelectorAll と getElementsByClassName の戻り値の違いを要素追加後の件数で比べ、null が返る原因をエラー文の再現と対処コードで整理しています。掲載した出力はすべて、Chromium 141(Playwright 1.56.0 から起動)で実際に動かして得たものです。

querySelectorの早見表

まずは全体像です。「1つだけほしいなら querySelector、全部ほしいなら querySelectorAll」 が基本です。

やりたいこと 書き方 戻り値 見つからないとき
最初の1つを取る document.querySelector('.item') Element null
全部取る document.querySelectorAll('.item') NodeList(静的) 空の NodeList(length が 0)
id で1つ取る document.getElementById('top') Element null
class で全部取る document.getElementsByClassName('item') HTMLCollection(ライブ) 空の HTMLCollection
ある要素の中だけ探す ul.querySelector('.sold') Element null
親方向に探す li.closest('ul') Element null
セレクタに合うか調べる li.matches('.item.sold') true / false false
全部に同じ処理をする document.querySelectorAll('.item').forEach(...) なし 0回実行(エラーなし)
配列メソッドを使う [...document.querySelectorAll('.item')].map(...) 配列 空の配列
null でも止めずに読む document.querySelector('.x')?.textContent 値 または undefined undefined

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JS querySelectorの使い方|All・nullの対処」

この記事で使う練習用HTML

以降の取得結果は、すべて次のHTMLを対象にしています。そのまま index.html として保存し、ブラウザの開発者ツールのコンソールで試せます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>querySelector 練習ページ</title>
</head>
<body>
  <header id="top">
    <h1 class="title">備品リスト</h1>
    <nav>
      <a href="/home" class="link">ホーム</a>
      <a href="https://example.com/help" class="link external" target="_blank">ヘルプ</a>
      <a href="/files/list.pdf" class="link">一覧PDF</a>
    </nav>
  </header>
  <main>
    <ul id="items" class="list">
      <li class="item" data-id="1" data-status="in-stock">ノート</li>
      <li class="item sold" data-id="2" data-status="sold-out">ペン</li>
      <li class="item" data-id="3" data-status="in-stock">付箋</li>
      <li class="item special" data-id="4">ホチキス</li>
      <li class="item" data-id="5" data-status="in-stock">はさみ</li>
    </ul>
    <form id="form">
      <input type="text" name="keyword" placeholder="検索語">
      <input type="checkbox" name="agree" checked>
      <input type="email" name="mail" disabled>
      <button type="submit" class="btn primary">検索</button>
    </form>
    <p class="note">在庫は毎朝更新します。</p>
    <div id="1st">数字で始まるid</div>
    <div id="user.name">ドットを含むid</div>
  </main>
</body>
</html>

querySelectorとquerySelectorAllの基本

1つ取る:querySelector

引数にはCSSセレクタを文字列で渡します。CSSで書くときと同じ記法(id は #、class は .)がそのまま使えます。

const title = document.querySelector('.title');
console.log(title.textContent);        // 備品リスト

const first = document.querySelector('.item');
console.log(first.textContent);        // ノート(5つあっても最初の1つ)

const none = document.querySelector('.nothing');
console.log(none);                     // null

一致する要素が複数あっても、返るのは文書の上から数えて最初の1つだけです。

全部取る:querySelectorAll

const items = document.querySelectorAll('.item');
console.log(items.length);             // 5
console.log(items[0].textContent);     // ノート

items.forEach((el, i) => {
  console.log(i, el.textContent);
});

実行結果:

0 ノート
1 ペン
2 付箋
3 ホチキス
4 はさみ

querySelectorAll は、一致が0件でも null にはならず、空の NodeList を返します。 そのため forEach をそのまま呼んでもエラーになりません(0回実行されるだけです)。

2つの違いを表で確認

比べる点 querySelector querySelectorAll
返すもの 最初の1要素 一致した全要素の NodeList
一致なしのとき null 空の NodeList(length は 0)
そのまま textContent を読む 読める undefined(一覧なので要素ではない)
そのまま addEventListener 使える TypeError(1つずつ登録する)
forEach 使えない(要素1つ) 使える
向いている場面 id 指定・1つしかない要素 リスト・複数のボタンやリンク

セレクタの書き方早見表(24例と取得結果)

上の練習用HTMLに対して、それぞれのセレクタを querySelector と querySelectorAll に渡した結果です。「件数」と「中身」は実際の出力をそのまま載せています。

id・class・タグ

やりたいこと セレクタ querySelector の結果 All の件数 All の中身
id で取る #top header 要素 1 header
class で取る .title 備品リスト 1 備品リスト
タグ名で取る li ノート 5 ノート / ペン / 付箋 / ホチキス / はさみ
class を2つとも持つ .item.sold ペン 1 ペン
複数のどれか(カンマ) h1, p 備品リスト 2 備品リスト / 在庫は毎朝更新します。
一致なし .nothing null 0 (空)

親子・兄弟の関係

やりたいこと セレクタ querySelector の結果 All の件数 All の中身
直下の子 ul > li ノート 5 ノート / ペン / 付箋 / ホチキス / はさみ
子孫(スペース区切り) nav a ホーム 3 ホーム / ヘルプ / 一覧PDF
直後の兄弟 h1 + nav nav 要素 1 nav
後ろにある兄弟すべて .sold ~ li 付箋 3 付箋 / ホチキス / はさみ
絞り込みを重ねる #form button.primary 検索 1 検索

属性

やりたいこと セレクタ querySelector の結果 All の件数 All の中身
属性を持つ [data-status] ノート 4 ノート / ペン / 付箋 / はさみ
属性値が一致 [data-status="sold-out"] ペン 1 ペン
値が〜で始まる [href^="https"] ヘルプ 1 ヘルプ
値が〜で終わる [href$=".pdf"] 一覧PDF 1 一覧PDF
値に〜を含む [href*="example"] ヘルプ 1 ヘルプ

順番・否定・状態(擬似クラス)

やりたいこと セレクタ querySelector の結果 All の件数 All の中身
最初の子 li:first-child ノート 1 ノート
最後の子 li:last-child はさみ 1 はさみ
n番目の子 li:nth-child(2) ペン 1 ペン
奇数番目 li:nth-child(odd) ノート 3 ノート / 付箋 / はさみ
〜ではない li:not(.sold) ノート 4 ノート / 付箋 / ホチキス / はさみ
属性を持たない li:not([data-status]) ホチキス 1 ホチキス
チェック済み input[type="checkbox"]:checked name が agree の input 1 agree
無効化されている input:disabled name が mail の input 1 mail

nth-child は1から数えます(配列の添字のように0からではありません)。odd は奇数番目、even は偶数番目、3n と書くと3の倍数番目です。

早見表を自分のページで試すコード

次のコードをコンソールに貼ると、同じ形式で結果を確認できます。

const selectors = ['#top', '.item.sold', '[data-status]', 'li:not(.sold)'];

for (const s of selectors) {
  const one = document.querySelector(s);
  const all = document.querySelectorAll(s);
  console.log(
    s,
    '| 1つ目:', one === null ? 'null' : one.textContent.trim().slice(0, 12),
    '| 件数:', all.length
  );
}

getElementByIdとの違いと使い分け

getElementById は id 専用、querySelector はCSSセレクタ全般を受け取ります。引数の書き方が違う点(# を付けるかどうか)が、取り違えやすいポイントです。

console.log(document.getElementById('top'));      // header 要素
console.log(document.getElementById('#top'));     // null(# は不要)
console.log(document.querySelector('#top'));      // header 要素
console.log(document.querySelector('top'));       // null(top というタグを探してしまう)

console.log(document.querySelector('#top') === document.getElementById('top')); // true

最後の行が true になるとおり、同じ要素を指していれば、どちらで取っても同じオブジェクトです。

比べる点 getElementById querySelector
引数 id の文字列そのまま('top') CSSセレクタ('#top')
呼び出せる場所 document document と各要素
条件の組み合わせ できない できる('#form button.primary' など)
見つからないとき null null
数字で始まる id そのまま取れる エスケープが必要(次の節)
向いている場面 id が決まっている1要素 class・属性・階層を組み合わせたい

id が決まっていて記号を含まないなら、どちらを使っても結果は同じです。チーム内でどちらかに統一しておくと読みやすくなります。条件を組み合わせる必要があるときは querySelector を選びます。

数字で始まるid・ドットを含むid

練習用HTMLには、id が 1st の要素と user.name の要素があります。これを querySelector で取ろうとすると、次の結果になりました。

document.getElementById('1st');              // div 要素(取れる)
document.querySelector('#1st');              // SyntaxError
document.querySelector('#user.name');        // null

実行結果(Chromium 141):

SyntaxError: Failed to execute 'querySelector' on 'Document': '#1st' is not a valid selector.

#1st はCSSの識別子として書けない形のため、null ではなく SyntaxError になります。#user.name は「id が user で、class に name を持つ要素」と解釈されるので、該当なしの null です。

対処は次の3通りです。変数で受け取った id を使うときは CSS.escape が便利です。

// 1. CSS.escape でエスケープする
document.querySelector('#' + CSS.escape('1st'));        // 取れる
console.log(CSS.escape('1st'));                         // \31 st

// 2. 属性セレクタで書く
document.querySelector('[id="user.name"]');             // 取れる

// 3. バックスラッシュでエスケープする(JS文字列では2つ重ねる)
document.querySelector('#user\\.name');                 // 取れる

NodeListとHTMLCollectionの違い

querySelectorAll が返すのは 静的な NodeList、getElementsByClassName が返すのは ライブな HTMLCollection です。「静的」は取得した時点の内容で固定されること、「ライブ」はページの変化に合わせて中身が自動で変わることを指します。

要素を追加したあとの件数を比べる

const nodeList = document.querySelectorAll('.item');          // 静的
const collection = document.getElementsByClassName('item');   // ライブ
const childNodes = document.getElementById('items').childNodes; // ライブな NodeList

console.log(`追加前: NodeList=${nodeList.length} HTMLCollection=${collection.length} childNodes=${childNodes.length}`);

const li = document.createElement('li');
li.className = 'item';
li.textContent = 'クリップ';
document.getElementById('items').appendChild(li);

console.log(`追加後: NodeList=${nodeList.length} HTMLCollection=${collection.length} childNodes=${childNodes.length}`);
console.log(`取り直し: ${document.querySelectorAll('.item').length}`);

実行結果:

追加前: NodeList=5 HTMLCollection=5 childNodes=11
追加後: NodeList=5 HTMLCollection=6 childNodes=12
取り直し: 6

querySelectorAll の結果は5件のまま、getElementsByClassName の結果は6件に増えました。 追加した要素も含めたいときは、querySelectorAll をもう一度呼んで取り直します。

childNodes が11件なのは、li 要素5つに加えて、改行やインデントのテキストノードが6つ含まれるためです。また、childNodes は同じ NodeList でもライブです。「NodeList だから静的」ではなく「querySelectorAll が返す NodeList が静的」 と覚えると正確です。

forEachが使える・使えない

console.log(typeof nodeList.forEach);     // function
console.log(typeof collection.forEach);   // undefined
console.log(Array.isArray(nodeList));     // false

collection.forEach((el) => console.log(el));
nodeList.map((el) => el.textContent);

実行結果:

TypeError: collection.forEach is not a function
TypeError: nodeList.map is not a function

NodeList は forEach を持ちますが配列ではないので、map・filter・reduce などは持っていません。HTMLCollection は forEach も持ちません。整理すると次のとおりです。

比べる点 NodeList(querySelectorAll) HTMLCollection(getElementsBy〜) 配列
取得後にページが変わると 変わらない(静的) 自動で変わる(ライブ) 変わらない
length・添字アクセス 使える 使える 使える
forEach 使える 使えない 使える
for...of 使える 使える 使える
entries() / keys() / values() 使える 使えない 使える
map / filter / reduce 使えない 使えない 使える
Array.isArray false false true

配列に変換して map・filter を使う

スプレッド構文か Array.from で配列に変換すれば、すべての配列メソッドが使えます。

// スプレッド構文
const ids = [...document.querySelectorAll('.item')].map((el) => el.dataset.id);
console.log(ids.join(','));        // 1,2,3,4,5

// Array.from(HTMLCollection にも使える)
const names = Array.from(document.getElementsByClassName('item')).map((el) => el.textContent);
console.log(names.join(','));      // ノート,ペン,付箋,ホチキス,はさみ,クリップ

// 添字つきで回したいとき
for (const [i, el] of document.querySelectorAll('.item').entries()) {
  console.log(i, el.textContent);
}

2つ目の結果に「クリップ」が入っているのは、先ほど要素を追加したあとに実行したためです。

ライブなコレクションをループで書き換えるとどうなるか

ライブなコレクションは、ループの途中で中身が変わります。全要素から class を外すつもりで書いたコードが、一部だけ残る例を見てみます。

// ライブな HTMLCollection を for で回して class を外す
const live = document.getElementsByClassName('item');
for (let i = 0; i < live.length; i++) {
  live[i].classList.remove('item');
}
console.log(document.querySelectorAll('.item').length);

実行結果(要素が6つある状態で実行):

3

残ったのは「ペン・ホチキス・クリップ」の3つでした。class を外した瞬間にその要素がコレクションから抜け、後ろの要素が前に詰まるため、1つおきに処理が飛びます。

静的な NodeList なら、同じ処理が全要素に行き渡ります。

document.querySelectorAll('.item').forEach((el) => el.classList.remove('item'));
console.log(document.querySelectorAll('.item').length);   // 0
選び方 向いている取得方法
取得した時点の要素に、まとめて処理をしたい querySelectorAll
ループ中に class の付け外しや要素の削除をする querySelectorAll
常に最新の件数を参照したい getElementsByClassName、または都度 querySelectorAll
map・filter で加工したい querySelectorAll を配列に変換

複数の要素にまとめて処理する書き方

querySelectorAll の戻り値は一覧なので、要素のプロパティやメソッドを直接呼ぶことはできません。 よくある3つの書き方と、その結果です。

document.querySelectorAll('.item').textContent;
document.querySelectorAll('.item').addEventListener('click', () => {});
document.querySelectorAll('.item').classList.add('x');

実行結果:

undefined
TypeError: document.querySelectorAll(...).addEventListener is not a function
TypeError: Cannot read properties of undefined (reading 'add')

1つ目はエラーにならず undefined が返るので、気づきにくい点です。forEach で1つずつ処理する形に直します。

// 直したあと:1つずつ処理する
document.querySelectorAll('.item').forEach((el) => {
  el.classList.add('x');
  el.addEventListener('click', () => {
    console.log(el.textContent);
  });
});

ある要素の中だけを探す

querySelector と querySelectorAll は、document だけでなく各要素からも呼べます。探す範囲をその要素の子孫に絞れるので、同じ class がページ内の別の場所にあっても取り違えません。

const ul = document.querySelector('#items');

console.log(ul.querySelector('.sold').textContent);   // ペン
console.log(ul.querySelector('h1'));                  // null(ul の外にある)

注意したいのは、セレクタの照合そのものは文書全体に対して行われる点です。次の例では、ul の外側にある main を含むセレクタでも一致しました。

console.log(ul.querySelector('main li').textContent); // ノート

「ul 自身を起点にした直下の子」のように書きたいときは、:scope を使います。:scope は呼び出し元の要素そのものを指します。

console.log(ul.querySelector(':scope > li').textContent);      // ノート
console.log(ul.querySelectorAll(':scope > .item').length);     // 6(クリップを追加したあとのため)

親方向に探す closest と、判定する matches

querySelector は子孫の方向にしか探しません。親の方向に探すときは closest を使います。

const li = document.querySelector('.sold');

console.log(li.closest('ul').id);           // items
console.log(li.matches('.item.sold'));      // true

closest は、あとで紹介するイベント委任でもよく使います。

nullが返る原因と対処

querySelector の結果が null のままプロパティを読むと、次のエラーで処理が止まります。

document.querySelector('.nothing').textContent;
TypeError: Cannot read properties of null (reading 'textContent')

このエラーが出たら、「読もうとしたプロパティ」ではなく「その手前の querySelector が null を返した」ことが原因です。 null になる主な原因は次の3つです。

原因 確かめ方 対処
script が要素より先に実行されている script タグの位置を見る defer を付ける / body の末尾に置く / DOMContentLoaded を待つ
セレクタの書き間違い コンソールで同じセレクタを実行する # と . の有無、大文字小文字、記号のエスケープを見直す
あとから追加される要素 追加の前後で実行してみる 追加後に取得する / イベント委任 / MutationObserver

原因1:scriptの位置(要素より先に実行されている)

ブラウザはHTMLを上から順に読みます。head の中に書いたスクリプトは、body の中の要素がまだ作られていない時点で実行されます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>n1</title>
<script>
  const btn = document.querySelector('#save');
  console.log('head内:', btn);
  btn.addEventListener('click', () => console.log('保存'));
</script>
</head>
<body>
  <button id="save">保存</button>
</body>
</html>

実行結果:

head内: null
Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

対処は3通りあり、どれも「要素ができてから実行する」ようにするものです。

対処A:外部ファイルにして defer を付ける

<head>
  <script src="app.js" defer></script>
</head>
<body>
  <button id="save">保存</button>
</body>
// app.js
console.log('defer:', document.querySelector('#save').outerHTML, '/ readyState =', document.readyState);

対処B:DOMContentLoaded を待つ、または body の末尾に書く

<head>
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const btn = document.querySelector('#save');
      console.log('DOMContentLoaded後:', btn.outerHTML);
    });
  </script>
</head>
<body>
  <button id="save">保存</button>
  <script>
    console.log('body末尾:', document.querySelector('#save').outerHTML);
  </script>
</body>

実行結果(対処AとBをそれぞれ実行):

defer: <button id="save">保存</button> / readyState = interactive
body末尾: <button id="save">保存</button>
DOMContentLoaded後: <button id="save">保存</button>

type="module" を付けたスクリプトも、defer と同じくHTMLの解析が終わってから実行されます。head の中に module のスクリプトを書いて同じ取得を試したところ、次のとおり要素が取れました。

module: <button id="save">保存</button> / readyState = interactive
書き方 実行されるタイミング 向いている場面
head に script(属性なし) その場で即実行(body はまだ無い) DOMに触れない初期設定
script に defer HTMLの解析が終わったあと 外部ファイルの基本形
script に type="module" HTMLの解析が終わったあと import を使う構成
body の末尾に script そこまでの要素が作られたあと 小さなインラインスクリプト
DOMContentLoaded の中 HTMLの解析が終わったあと script の位置を変えられないとき

原因2:セレクタの書き間違い

練習用HTMLに対して、よくある書き間違いを実行した結果です。ほとんどはエラーにならず、静かに null が返ります。

書いたコード 結果 理由 正しい書き方
querySelector('top') null # が無く、top タグを探している '#top'
querySelector('item') null . が無く、item タグを探している '.item'
querySelector('#Top') null id は大文字と小文字を区別する '#top'
querySelector('.Title') null class も大文字と小文字を区別する '.title'
getElementById('#top') null getElementById に # は不要 'top'
querySelector('#user.name') null . 以降が class と解釈される '[id="user.name"]'
querySelector('#1st') SyntaxError 数字で始まる id はそのまま書けない '#' + CSS.escape('1st')
querySelector('') SyntaxError 空文字は渡せない セレクタを渡す

空文字を渡したときのエラー全文は次のとおりです。変数にセレクタを入れて渡す場合、変数が空になっているとこのエラーになります。

SyntaxError: Failed to execute 'querySelector' on 'Document': The provided selector is empty.

切り分けの手順は、開発者ツールのコンソールで同じセレクタを直接実行することです。 コンソールで取れるのにスクリプトでは null になるなら原因1か原因3、コンソールでも null ならセレクタの書き方が原因です。

原因3:あとから追加した要素

JavaScript で追加する要素は、追加する前には存在しません。次のHTMLで、読み込み時とボタンを押したあとの違いを確かめます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>n4</title>
</head>
<body>
<ul id="list"><li class="row">A</li></ul>
<button id="add">追加</button>
<script>
  const list = document.querySelector('#list');

  // (1) 読み込み時に取得して個別に登録
  document.querySelectorAll('.row').forEach((li) => {
    li.addEventListener('click', () => console.log('個別登録:', li.textContent));
  });

  // (2) 親に1つだけ登録(イベント委任)
  list.addEventListener('click', (e) => {
    const li = e.target.closest('.row');
    if (li) console.log('委任:', li.textContent);
  });

  console.log('追加前に .new を探す:', document.querySelector('.new'));

  document.querySelector('#add').addEventListener('click', () => {
    const li = document.createElement('li');
    li.className = 'row new';
    li.textContent = 'B';
    list.appendChild(li);
    console.log('追加後に .new を探す:', document.querySelector('.new').textContent);
  });
</script>
</body>
</html>

「A をクリック → 追加ボタンをクリック → B をクリック」の順に操作した実行結果:

追加前に .new を探す: null
個別登録: A
委任: A
追加後に .new を探す: B
委任: B

読み取れることは2つです。

  • 追加する前は null、追加したあとは取れる。 取得するタイミングを、追加の処理のあとに移せば解決します。
  • 読み込み時に forEach で登録したクリック処理は、あとから追加した B には付いていない(「個別登録: B」が出ていない)。親要素に1つだけ登録するイベント委任なら、B のクリックも拾えています。

イベント委任は、親要素でイベントを受け取り、e.target.closest('.row') で「クリックされた場所から見て一番近い対象要素」を探す書き方です。増減するリストには、この形が扱いやすくなります。

追加されるタイミングがわからないとき:MutationObserver

通信の結果を待って描画する部品など、いつ要素が追加されるか自分のコードからわからない場合は、MutationObserver(DOMの変化を監視する仕組み)で追加を待てます。

<div id="app"></div>
<script>
  const app = document.querySelector('#app');

  const observer = new MutationObserver(() => {
    const el = document.querySelector('.late');
    if (el) {
      console.log('MutationObserverで検出:', el.textContent);
      observer.disconnect();   // 見つけたら監視を終える
    }
  });
  observer.observe(app, { childList: true, subtree: true });

  console.log('直後:', document.querySelector('.late'));

  // 0.3秒後に要素が追加される想定
  setTimeout(() => {
    app.innerHTML = '<p class="late">あとから表示</p>';
  }, 300);
</script>

実行結果:

直後: null
MutationObserverで検出: あとから表示

nullでも止まらない書き方

要素が無いこともありうる場合は、null を前提にした書き方にしておくと、ほかの処理まで止まることを防げます。

// 1. if で確かめる(基本形)
const note = document.querySelector('.note');
if (note) {
  note.textContent = '更新しました';
}

// 2. オプショナルチェーン(?.)で読む
const text = document.querySelector('.nothing')?.textContent;
console.log(text);                       // undefined(エラーにならない)

// 3. 既定値を付ける(??)
const label = document.querySelector('.nothing')?.textContent ?? '(未設定)';

// 4. メソッド呼び出しにも使える
document.querySelector('.nothing')?.addEventListener('click', () => {});

// 5. 必ずあるはずの要素は、見つからないことを早めに知らせる
function mustQuery(selector) {
  const el = document.querySelector(selector);
  if (el === null) {
    throw new Error(`要素が見つかりません: ${selector}`);
  }
  return el;
}
状況 向いている書き方
ページによって要素があったり無かったりする if または ?.
無いときの代わりの値がある ?. と ?? の組み合わせ
必ずあるはずで、無いなら設定の誤り 専用の関数で、セレクタ名つきのエラーを投げる
複数の要素に処理する querySelectorAll と forEach(0件でもエラーにならない)

オプショナルチェーンは代入の左辺には使えません(el?.textContent = 'x' は構文エラー)。値を書き換えるときは if で確かめる形にします。

よく使う取得パターン集

そのまま貼って使える形でまとめます。

// フォームの入力値を取る
const keyword = document.querySelector('#form input[name="keyword"]').value;

// チェックされたチェックボックスの name を集める
const checked = [...document.querySelectorAll('input[type="checkbox"]:checked')]
  .map((el) => el.name);

// data 属性で絞り込んで1つ取る
const id = 3;
const target = document.querySelector(`.item[data-id="${id}"]`);

// 外部リンクすべてに rel を付ける
document.querySelectorAll('a[href^="https"]').forEach((a) => {
  a.rel = 'noopener';
});

// 在庫ありの件数を数える
const count = document.querySelectorAll('.item[data-status="in-stock"]').length;

// 売り切れ以外に class を付ける
document.querySelectorAll('.item:not(.sold)').forEach((el) => {
  el.classList.add('available');
});

変数をセレクタに埋め込むときは、値に記号が入る可能性があるなら CSS.escape を通すと安心です。

const userId = 'user.name';
const el = document.querySelector('#' + CSS.escape(userId));

つまずいたときのチェックリスト

確認すること 見るポイント
エラー文に null とあるか 手前の querySelector が何も見つけていない
コンソールで同じセレクタを実行したか コンソールで取れるなら実行タイミングが原因
# と . を付けたか getElementById は付けない、querySelector は付ける
大文字と小文字は合っているか id・class は区別される
script はどこにあるか head にあるなら defer か DOMContentLoaded
その要素はあとから追加されるものか 追加後に取得する、または親に委任する
querySelectorAll の戻り値に直接処理していないか forEach で1つずつ処理する
is not a function と出たか HTMLCollection に forEach、NodeList に map を呼んでいないか

よくある質問

Q. querySelector と getElementById はどちらを使えばよいですか? A. id で1つ取るだけなら結果は同じなので、どちらでも構いません。class や属性、階層を組み合わせたいときは querySelector を選びます。数字で始まる id や記号を含む id は、getElementById ならそのまま取れ、querySelector では CSS.escape か属性セレクタが必要です。

Q. querySelectorAll の結果に map が使えないのはなぜですか? A. 戻り値の NodeList は配列ではないためです。forEach・entries・for...of は使えますが、map や filter は持っていません。[...document.querySelectorAll('.item')] または Array.from で配列に変換すると使えるようになります。

Q. 要素は画面に表示されているのに null になります。なぜですか? A. スクリプトが実行された時点では、まだその要素が作られていなかった可能性があります。script が head にあるなら defer を付けるか DOMContentLoaded を待ちます。JavaScript であとから追加される要素なら、追加後に取得するか、親要素へのイベント委任に切り替えます。

Q. querySelectorAll で取ったあとに追加した要素も、一覧に入りますか? A. 入りません。querySelectorAll の NodeList は取得した時点で固定されます(今回の検証では、追加後も5件のままでした)。追加後の要素も含めたいときは、もう一度 querySelectorAll を呼びます。

まとめ

  • querySelector は最初の1つ、querySelectorAll は全部を返す。見つからないとき、前者は null、後者は空の NodeList
  • セレクタはCSSと同じ記法。id は #、class は .、属性は角かっこ、否定は :not、順番は :nth-child(1から数える)
  • querySelectorAll の NodeList は静的で、要素を追加しても件数は変わらない。getElementsByClassName の HTMLCollection はライブで自動的に増減する
  • forEach は NodeList で使え、HTMLCollection では使えない。map・filter を使うときはスプレッド構文か Array.from で配列にする
  • null の原因は「script の位置」「セレクタの書き間違い」「あとから追加した要素」 の3つに分けて確かめる
  • null でも止まらない書き方は、if・オプショナルチェーン・イベント委任を場面に合わせて選ぶ
  • 数字で始まる id や記号を含む id は CSS.escape か属性セレクタで取る

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JS querySelectorの使い方|All・nullの対処」

株式会社RJCでは、querySelectorをはじめとするDOM操作の基本を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?