Halaman toko Budi berisi puluhan elemen: judul, daftar produk, panel stok, tombol keranjang. Sebelum JavaScript bisa mengubah salah satunya — mengganti teks judul, menandai produk unggulan, memperbarui daftar — kode harus memegang elemen yang tepat terlebih dahulu. Memilih elemen (element selection) adalah langkah pertama dari hampir setiap pekerjaan DOM, dan browser menyediakan beberapa method dengan karakter yang berbeda.

Chapter ini menuntaskan tiga cara utama — getElementById, querySelector, dan querySelectorAll — membedakan koleksi live dan static, serta menangani kasus ketika pencarian tidak menemukan apa pun. Halaman contohnya bisa diunduh sebagai halaman-toko.txt.

Tiga Cara Memilih Elemen #

html halaman-toko.txt
<!DOCTYPE html>
<html>
  <body>
    <h1 id="judul">Toko Kopi Budi</h1>
    <ul class="produk-list">
      <li class="produk">Kopi Gayo</li>
      <li class="produk unggulan">Teh Melati</li>
      <li class="produk">Gula Aren</li>
    </ul>
    <p class="info">Stok diperbarui setiap pagi.</p>
    <script src="cari-elemen.js"></script>
  </body>
</html>

Berkas berikut memakai ketiga method di atas pada halaman yang sama. Perhatikan bahwa setiap cara punya bentuk argumen berbeda: getElementById menerima nama id tanpa tanda pagar, sedangkan kedua method querySelector menerima selector CSS utuh.

// cari-elemen.js — tiga cara memilih elemen dari halaman toko

// 1. getElementById: satu id, satu elemen
const judul = document.getElementById("judul");
console.log(judul.textContent);

// 2. querySelector: selector CSS, kecocokan pertama
const unggulan = document.querySelector(".produk.unggulan");
console.log(unggulan.textContent);
const terakhir = document.querySelector(".produk-list li:last-child");
console.log(terakhir.textContent);

// 3. querySelectorAll: semua kecocokan (static NodeList)
const produk = document.querySelectorAll(".produk");
console.log(produk.length);
produk.forEach((item, urutan) => {
  console.log(`${urutan + 1}. ${item.textContent}`);
});

// 4. ketika tidak ada yang cocok
const promo = document.querySelector("#promo");
console.log(promo);
console.log(promo?.textContent);

Output yang Dihasilkan (console browser):

text
Toko Kopi Budi
Teh Melati
Gula Aren
3
1. Kopi Gayo
2. Teh Melati
3. Gula Aren
null
undefined

Keluaran di atas dicatat dari eksekusi nyata berkas ini pada lingkungan DOM jsdom; console browser menghasilkan baris-baris yang sama. Mari kita bedah:

  1. Baris 4 (document.getElementById("judul")): method ini mencari satu elemen yang atribut id-nya persis "judul" dan mengembalikan elemen tersebut. Id dijamin unik per dokumen, sehingga pencariannya langsung dan hasilnya paling dapat diprediksi.
  2. Baris 8 (.produk.unggulan): querySelector menerima selector CSS apa pun — di sini “elemen yang punya class produk DAN unggulan sekaligus”. Dari semua kecocokan, hanya yang pertama dalam urutan dokumen yang dikembalikan, yaitu Teh Melati.
  3. Baris 10 (.produk-list li:last-child): selector bisa bersarang dengan kombinator dan pseudo-class CSS — “elemen li terakhir di dalam .produk-list”. Kemampuan inilah yang membuat querySelector sangat fleksibel dibanding method pencarian sederhana.
  4. Baris 14–17 (querySelectorAll dan forEach): method ini mengembalikan NodeList berisi seluruh kecocokan. NodeList punya method forEach, sehingga daftar bisa dijajal tanpa konversi apa pun; argumen kedua callback memberi nomor urut otomatis.
  5. Baris 21–23 (#promo yang tidak ada): querySelector mengembalikan null ketika tidak menemukan apa pun. Baris promo?.textContent — optional chaining dari section Operators — mengembalikan undefined alih-alih melempar galat, sehingga program tetap berlanjut.

Mencari di Dalam Subtree #

Method querySelector dan querySelectorAll tidak hanya milik document — setiap elemen punya keduanya. Ketika dipanggil dari sebuah elemen, pencarian dibatasi pada subtree milik elemen tersebut: hanya keturunannya yang diperiksa, dan pencarian tidak pernah naik ke atas.

js Pencarian terbatas pada satu subtree
const daftar = document.querySelector(".produk-list");
console.log(daftar.querySelectorAll("li").length); // 3
console.log(daftar.querySelector("#judul"));       // null — di luar subtree

Batasan ini berguna dalam dua situasi. Pertama, presisi: panel.querySelector("button") hanya melihat tombol di dalam panel, meski halaman penuh tombol lain. Kedua, komponen: dua bagian halaman boleh sama-sama punya elemen ber-id atau ber-class serupa, dan masing-masing kode tetap menemukan punyanya karena pencarian dibatasi dari akar yang berbeda.

Sebaliknya, document.querySelector(...) mengarungi seluruh dokumen karena akarnya adalah dokumen itu sendiri. Kebiasaan yang sehat: mulai dari document untuk titik masuk pertama, lalu turunkan lingkup pencarian mengikuti struktur halaman yang sedang dikerjakan.

Live vs Static: Koleksi yang Berubah Sendiri #

querySelectorAll mengembalikan koleksi static: daftar yang dicuplik (snapshot) pada saat pemanggilan, lalu tidak berubah lagi meski dokumen berubah. Ada keluarga method lain — getElementsByTagName dan getElementsByClassName — yang mengembalikan HTMLCollection live: koleksi yang selalu sinkron dengan kondisi dokumen terkini.

MethodKoleksiSifatIsi
querySelectorAll()NodeListstatic — cuplikan saat dipanggilhanya elemen
getElementsByTagName()HTMLCollectionlive — mengikuti dokumenhanya elemen
getElementsByClassName()HTMLCollectionlive — mengikuti dokumenhanya elemen

Perbedaannya terlihat ketika dokumen berubah setelah koleksi dibuat. Misalkan satu li.produk baru ditambahkan ke daftar toko setelah dua koleksi berikut dibuat:

js Live vs static (ilustrasi; bukti lengkap diuji pada chapter Creating Elements)
const statis = document.querySelectorAll(".produk");
const hidup = document.getElementsByClassName("produk");

// (satu li.produk baru ditambahkan ke dokumen di sini)

console.log(statis.length); // 3 — snapshot saat dipanggil, tidak bergeser
console.log(hidup.length);  // 4 — koleksi live ikut memperbarui diri

Method penambah elemen pada potongan di atas belum kita pelajari, sehingga snippet ini sengaja kita jalankan penuh dan buktikan keluarannya di chapter Creating Elements. Untuk saat ini, cukup pegang aturannya: koleksi static aman dari perubahan dokumen, koleksi live selalu mencerminkan kondisi terbaru.

Kapan koleksi live justru diinginkan? Ketika kita ingin penghitungan yang selalu segar tanpa memanggil ulang querySelectorAll setiap saat — misalnya getElementsByClassName("produk") yang panjangnya bisa dibaca kapan saja dan selalu benar. Namun kejutan live juga punya sisi buruk: koleksi yang bergeser sendiri di tengah perulangan pernah menghasilkan bug klasik saat menghapus item, dan kisah itu kita bedah di chapter Removing Elements. Sampai saat itu, pilih static sebagai kebiasaan bawaan.

Ketika Tidak Ada yang Cocok #

Kegagalan pencarian punya dua wajah berbeda, dan keduanya perlu dibedakan:

  • querySelector dan getElementById mengembalikan null — nilai yang harus diperiksa sebelum dipakai, karena memanggil properti apa pun pada null menghasilkan TypeError dan menghentikan sisa script.
  • querySelectorAll (juga kedua method getElementsBy*) mengembalikan koleksi kosong berukuran 0 — bukan null. Pemanggilan forEach pada koleksi kosong berjalan tanpa galat; loop-nya hanya tidak pernah dieksekusi.
js Koleksi kosong vs null
console.log(document.querySelectorAll("#promo").length); // 0
console.log(document.getElementById("promo"));           // null

Pola penjagaan yang nyaman: bila elemen wajib ada, izinkan TypeError muncul agar kesalahan struktur terlihat saat pengembangan; bila elemen mungkin sengaja tidak ada, gunakan optional chaining (elemen?.teks) atau periksa keberadaannya dengan if terlebih dahulu.

Perhatikan juga beda bentuk kegagalan ketika niat kita “baca jika ada”: querySelectorAll("#promo").length mengembalikan angka yang bisa langsung dibandingkan (0 berarti kosong), sedangkan querySelector("#promo") menuntut pemeriksaan null sebelum dipakai. Memilih method berarti memilih bentuk pemeriksaannya.

Dengan elemen sudah terpilih, pertanyaan berikutnya adalah bergerak dari satu elemen ke tetangganya tanpa mencari ulang dari document — itulah bahasan chapter Traversing DOM.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

cari-elemen.js

Unduh berkas
javascript
// cari-elemen.js — tiga cara memilih elemen dari halaman toko

// 1. getElementById: satu id, satu elemen
const judul = document.getElementById("judul");
console.log(judul.textContent);

// 2. querySelector: selector CSS, kecocokan pertama
const unggulan = document.querySelector(".produk.unggulan");
console.log(unggulan.textContent);
const terakhir = document.querySelector(".produk-list li:last-child");
console.log(terakhir.textContent);

// 3. querySelectorAll: semua kecocokan (static NodeList)
const produk = document.querySelectorAll(".produk");
console.log(produk.length);
produk.forEach((item, urutan) => {
  console.log(`${urutan + 1}. ${item.textContent}`);
});

// 4. ketika tidak ada yang cocok
const promo = document.querySelector("#promo");
console.log(promo);
console.log(promo?.textContent);