Belajar dengan membaca secara berurutan

DOM Tree dan Mengambil Element — querySelector dan querySelectorAll

Membahas querySelector dan querySelectorAll untuk mengambil element dari DOM tree: mencari lewat class, id, dan atribut data, mengubah NodeList jadi array, dan ?. untuk element yang tidak ada.

Anggap kamu ingin membaca harga semua produk di sebuah daftar produk. Mencari lewat id seperti #total hanya bisa dipakai untuk element yang punya id, jadi tidak cocok untuk daftar produk yang strukturnya sama semua.

Artikel ini membahas selector untuk mencari element dengan kondisi selain id, querySelectorAll untuk mengumpulkan semua yang cocok, dan cara membaca element yang mungkin tidak ada.

Mengambil Satu Element — querySelector dan Selector

Sekarang misalkan kamu hanya ingin membaca judul pengumuman penting dari sebuah daftar pengumuman. Daftar itu berisi li dengan bentuk yang sama, dan tidak satu pun punya id. Sebuah id hanya boleh dipakai oleh satu element di halaman, jadi id tidak bisa dijadikan penanda untuk sekelompok item sejenis, seperti "pengumuman penting".

Selector (string yang menuliskan kondisi element yang dicari) menentukan element lewat nama tag seperti li, lewat id seperti #news, atau lewat class (nama yang diberikan ke semua element sejenis) seperti .notice. document.querySelector(selector) mengembalikan element pertama di DOM tree yang cocok dengan kondisi itu.

// Siapkan daftar pengumuman (satu element bisa punya beberapa class, dipisahkan spasi)
document.body.innerHTML = `<ul id="news">
  <li class="notice">Perubahan ongkos kirim</li>
  <li class="notice important">Maintenance darurat</li>
  <li class="notice">Produk baru bulan ini</li>
</ul>`;

// Cari lewat nama tag / lewat class (nama class ditulis setelah .)
console.log(document.querySelector("li").textContent);           // Perubahan ongkos kirim
console.log(document.querySelector(".notice").textContent);      // Perubahan ongkos kirim
console.log(document.querySelector(".important").textContent);   // Maintenance darurat
Element yang Ditemukan Tiap Selector
Element body — dicari dari atas ke bawah
Element ul — id-nya news
li ke-1 — class-nya notice
  • "li" dan ".notice" — keduanya berhenti mencari di sini
  • Teksnya "Perubahan ongkos kirim"
li ke-2 — class-nya notice dan important
  • ".important" — berhenti mencari di sini
  • Teksnya "Maintenance darurat"
li ke-3 — class-nya notice
  • Teksnya "Produk baru bulan ini"
  • Tidak ada selector yang mencari sampai ke sini
Ada 3 li dengan class notice, tapi ".notice" berhenti mencari di li pertama. Yang dikembalikan adalah element pertama yang cocok.

li kedua punya dua class, jadi cocok dengan .notice maupun .important. Untuk mengambil li kedua saja, tulis kondisi yang hanya cocok dengan li itu, misalnya .important.

Dari halaman daftar produk, ambil element satu per satu dengan memilih antara nama tag, class, dan id. HTML-nya sudah disiapkan lewat assignment ke document.body.innerHTML di baris paling atas.

① Cari produk pertama lewat nama tag, lalu tampilkan teksnya.

② Cari produk lewat class yang hanya dimiliki produk rekomendasi, lalu tampilkan teksnya.

③ Cari element yang menampilkan jumlah produk lewat id-nya, lalu tampilkan teksnya.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Menelusuri Seluruh Daftar — querySelectorAll dan NodeList

Bagaimana kalau kamu ingin membaca judul semua pengumuman sekaligus? querySelector berhenti mencari di element pertama yang cocok, jadi berapa kali pun kamu memanggilnya dengan selector yang sama, hasilnya selalu "Perubahan ongkos kirim" dan tidak pernah sampai ke pengumuman kedua dan seterusnya.

document.querySelectorAll(selector) mengembalikan semua element yang cocok dalam bentuk NodeList (object mirip array yang berisi element yang ditemukan, berurutan sesuai DOM tree). Jumlahnya bisa dibaca dengan length, dan element-nya bisa diproses satu per satu dengan forEach.

document.body.innerHTML = `<ul id="news">
  <li class="notice">Perubahan ongkos kirim</li>
  <li class="notice important">Maintenance darurat</li>
  <li class="notice">Produk baru bulan ini</li>
</ul>`;

// Ambil semua element yang cocok sebagai NodeList
const notices = document.querySelectorAll(".notice");
console.log(notices.length);   // 3
notices.forEach((notice) => console.log(notice.textContent));
// Perubahan ongkos kirim
// Maintenance darurat
// Produk baru bulan ini

// notices.map(...) melempar TypeError. Ubah dulu menjadi array, baru panggil map
const titles = [...notices].map((notice) => notice.textContent);
console.log(titles.join(" / "));   // Perubahan ongkos kirim / Maintenance darurat / Produk baru bulan ini
Ubah Menjadi Array Sebelum Memanggil map
notices.map(...)NodeList tidakpunya mapMelemparTypeErrortitles tidakpernah dibuat[...notices].map(...)Array berisi3 element tadimap milik arrayjalan per item3 string:["Perubahanongkos kirim", …]
Memanggil map langsung pada NodeList melempar error, sedangkan setelah diubah menjadi array, map berjalan. Panggil map dan filter setelah NodeList diubah menjadi array.

Meskipun masih berupa NodeList, kamu bisa membaca satu element lewat index-nya, seperti notices[1]. Array yang dibuat dengan spread syntax adalah object terpisah, jadi setelah diubah pun notices tetap NodeList dan masih bisa dipakai dengan forEach.

Dari halaman rincian pembelian, baca semua barang yang dibeli sekaligus, lalu cek jumlahnya dan berapa barang yang harganya 2.000 yen atau lebih. HTML rinciannya sudah disiapkan lewat assignment ke document.body.innerHTML di baris paling atas.

① Ambil semua element barang di rincian, lalu tampilkan jumlahnya.

② Telusuri element yang sudah diambil satu per satu, lalu tampilkan teks tiap barang secara berurutan.

③ Ambil semua element harga, ubah menjadi array, saring yang harganya 2.000 yen atau lebih, lalu tampilkan jumlahnya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Menunjuk Satu Item Lewat Nomornya — Atribut data dan Selector Atribut

Sekarang kamu hanya butuh pengumuman bernomor N-103. Kalau pengumuman yang sama juga ditampilkan di bagian lain halaman, id-nya akan dobel, jadi id tidak bisa dipakai. Class adalah nama untuk mengelompokkan element sejenis, jadi juga bukan tempat yang tepat untuk menyimpan nomor.

Atribut data (atribut yang namanya diawali data-) bisa menyimpan value apa saja yang kamu tentukan sendiri, misalnya nomor. Selector yang mencari lewat atribut ditulis di dalam kurung siku: [data-id] cocok dengan element yang punya atribut itu, dan [data-id="N-103"] hanya cocok dengan element yang value-nya juga sama.

document.body.innerHTML = `<ul id="news">
  <li class="notice" data-id="N-101">Perubahan ongkos kirim</li>
  <li class="notice important" data-id="N-102">Maintenance darurat</li>
  <li class="notice" data-id="N-103">Produk baru bulan ini</li>
</ul>`;

// Cari hanya berdasarkan ada tidaknya atribut (ketiganya cocok)
console.log(document.querySelector("[data-id]").textContent);          // Perubahan ongkos kirim

// Cari element yang value-nya juga cocok (value diapit ", jadi seluruh selector diapit ')
console.log(document.querySelector('[data-id="N-103"]').textContent);  // Produk baru bulan ini
Dari Atribut Menjadi String Selector
data-id="N-103"[data-id="N-103"]'[data-id="N-103"]'Atribut di li:nama="value"Diapit kurung sikujadi selectorBerisi ", makadiapit '
Atribut HTML yang diapit kurung siku menjadi selector, lalu diapit ' untuk diberikan ke JavaScript. Tanda " di sekitar value dan ' di luar punya tugas yang berbeda.

[data-id] tanpa value cocok dengan ketiga li dan mengembalikan yang pertama, sedangkan [data-id="N-103"] yang ditulis lengkap dengan value-nya hanya menunjuk li yang nomornya sama. Tabel di bawah merangkum selector yang dipakai di artikel ini dan element yang dikembalikan masing-masing dari daftar pengumuman.

SelectorCocok denganHasil dari daftar
liTag-nya lili ke-1 (Perubahan ongkos kirim)
.importantclass berisi importantli ke-2 (Maintenance darurat)
#newsid-nya newsElement ul
[data-id]Punya atribut data-idli ke-1 (Perubahan ongkos kirim)
[data-id="N-103"]data-id-nya N-103li ke-3 (Produk baru bulan ini)

Value yang Diawali Angka Gagal Tanpa Tanda Kutip

Ada value yang tetap jalan tanpa tanda kutip, seperti [data-id=N-103], tapi value yang diawali angka, seperti [data-id=102], tidak bisa dibaca sebagai selector, dan baris itu melempar error "is not a valid selector". Selalu apit value-nya dengan ".

Di daftar stok sebuah toko alat tulis, ambil barang yang kamu cari berdasarkan nomor produk dan penanda diskonnya. HTML daftar stoknya sudah disiapkan lewat assignment ke document.body.innerHTML di baris paling atas.

① Cari element dengan nomor produk P-203, lalu tampilkan teksnya.

② Cari element yang punya atribut diskon, lalu tampilkan teksnya.

③ Ambil semua element yang punya atribut nomor produk, lalu tampilkan jumlahnya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Tetap Jalan Saat Element Tidak Ada — null dan ?.

Bayangkan halaman utama yang menampilkan judul pengumuman darurat hanya kalau pengumuman itu ada. Di hari tanpa pengumuman darurat, element-nya juga tidak ada, sehingga baris yang membaca teks judulnya melempar TypeError dan kode setelahnya tidak dijalankan.

Kalau tidak ada element yang cocok, querySelector mengembalikan null. Kalau kamu menambahkan optional chaining ?. pada hasilnya sebelum membaca .textContent, pembacaan berhenti di situ saat hasilnya null, dan yang dikembalikan adalah undefined. Dengan menambahkan ?? setelahnya, kamu bisa menentukan default value.

// Daftar yang belum punya pengumuman darurat (class-nya urgent)
document.body.innerHTML = `<ul><li class="notice">Produk baru bulan ini</li></ul>`;
const urgent = document.querySelector(".urgent");
console.log(urgent);                               // null
// urgent.textContent akan melempar TypeError
console.log(urgent?.textContent);                  // undefined
console.log(urgent?.textContent ?? "Tidak ada");   // Tidak ada

// Di daftar yang punya pengumuman darurat, bandingkan selector dengan dan tanpa titik (.)
document.body.innerHTML = `<ul><li class="urgent">Gangguan server</li></ul>`;
console.log(document.querySelector(".urgent")?.textContent ?? "Tidak ada");   // Gangguan server
console.log(document.querySelector("urgent")?.textContent ?? "Tidak ada");    // Tidak ada
Tiga Pencarian dan Teks yang Ditampilkan
querySelector(…)?.textContent?? "Tidak ada"Tanpa urgent,cari ".urgent"Ada urgent,cari ".urgent"Ada urgent,cari "urgent"Dapat null,?. tidak lanjutli ditemukan,teksnya dibacaCari tag urgent,dapat nullTampil "Tidak ada"Tampil "Gangguanserver"Ada, tapi tampil"Tidak ada"
Kolom kiri dan kanan sama-sama mendapat null, lalu menampilkan "Tidak ada". ?. tidak bisa membedakan element yang tidak ada dari salah ketik di selector.

Pakai ?. hanya untuk element yang di hari tertentu bisa tidak ada, seperti pengumuman darurat. Kalau kamu memakainya juga untuk element yang selalu ada, salah ketik tidak akan ketahuan. Jadi untuk element seperti itu, jangan pakai ?., biarkan kodenya melempar TypeError, lalu lacak penyebabnya dari nomor baris.

NodeList dengan 0 Element Bukan null

Meskipun tidak ada satu pun yang cocok, querySelectorAll tidak mengembalikan null, melainkan NodeList yang length-nya 0. Memanggil forEach padanya tidak menimbulkan error dan tidak melakukan apa-apa, jadi untuk mengetahui bahwa tidak ada yang cocok, cek length === 0, bukan dengan ?..

Di halaman detail produk, baca ulasan dan harga diskon, yang keduanya bisa saja tidak ada. HTML detail produknya sudah disiapkan lewat assignment ke document.body.innerHTML di baris paling atas.

① Tampilkan teks nama produk.

② Ambil semua li ulasan, dan kalau jumlahnya 0, tampilkan "Belum ada ulasan".

③ Tampilkan teks harga diskon dengan cara yang tidak melempar error meskipun element-nya tidak ada.

④ Kalau harga diskon tidak ada, tampilkan teks harga normal.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Kalau ada 3 li yang punya data-id, apa yang dikembalikan querySelector("[data-id]")?

Soal 2Apa yang terjadi kalau map dipanggil langsung pada hasil querySelectorAll(".notice")?

Soal 3Kalau element-nya tidak ada, apa hasil querySelector(".sale-price")?.textContent?