Daftar tugas milik Budi hidup dua arah: tugas baru masuk, tugas yang selesai keluar. Chapter Creating Elements sudah membahas sisi masuknya; sisi keluar punya jalan sendiri — menghapus satu elemen, menggantinya di posisi yang sama, atau mengosongkan seluruh kontainer sekaligus. Setiap jalan punya nama method dan perilaku tersendiri yang perlu dibedakan dengan tepat.

Chapter ini juga menyentuh satu fakta yang sering dianggap sepele: “dihapus dari halaman” tidak sama dengan “hilang dari memori”. Node yang sudah lepas dari dokumen bisa tetap hidup di sebuah variabel — dan itu bisa dimanfaatkan, sekaligus menjadi sumber kebingungan bila tidak disadari.

remove: Menghapus dari Dalam #

// bersihkan-daftar.js — menghapus dan mengganti elemen

const daftar = document.querySelector("#tugas");

// 1. remove: elemen menghapus dirinya sendiri
daftar.querySelector(".selesai").remove();
console.log(daftar.children.length); // 3

// 2. replaceWith: menukar dengan elemen baru
const pengganti = document.createElement("li");
pengganti.textContent = "Pesan kemasan ramah lingkungan";
daftar.querySelector(".perlu-ubah").replaceWith(pengganti);
console.log(daftar.children[0].textContent);

// 3. removeChild: bentuk lama, butuh induk, mengembalikan node
const lama = daftar.querySelector(".lama");
const kembali = daftar.removeChild(lama);
console.log(kembali === lama);        // true
console.log(daftar.children.length);  // 2

// 4. node terhapus masih hidup di variabel
console.log(kembali.textContent);     // Perbarui harga
daftar.append(kembali);               // bisa dipasang kembali!
console.log(daftar.children.length);  // 3

// 5. mengosongkan seluruh kontainer
daftar.innerHTML = "";
console.log(daftar.children.length);  // 0

Halaman contohnya adalah daftar <ul id="tugas"> berisi empat item dengan class berbeda — .selesai, .perlu-ubah, .lama, dan satu item tanpa class.

Output yang Dihasilkan (console browser):

text
3
Pesan kemasan ramah lingkungan
true
2
Perbarui harga
3
0

Keluaran dicatat dari eksekusi nyata di lingkungan jsdom dan identik dengan console browser. Mari kita bedah:

  1. Baris 6–7 (remove): method remove() dipanggil pada elemen itu sendiri — daftar.querySelector(".selesai") langsung menghapus dirinya dari induknya. Tidak perlu pegangan ke induk; jumlah anak daftar turun dari 4 menjadi 3.
  2. Baris 10–13 (replaceWith): elemen baru dibuat dengan createElement, lalu .replaceWith(pengganti) menukar elemen .perlu-ubah pada posisinya. Pembacaan daftar.children[0] mengonfirmasi slot pertama kini diisi teks pengganti — posisi tetap, isinya berganti.
  3. Baris 16–19 (removeChild): cara lama ini bekerja dari sisi induk: daftar.removeChild(lama). Method ini mengembalikan node yang baru saja dilepas — kembali === lama bernilai true — dan jumlah anak kini 2.
  4. Baris 22–24 (node lepas masih utuh): node yang dipegang variabel kembali masih lengkap dengan isinya (Perbarui harga). Penugasan daftar.append(kembali) memasangnya kembali ke halaman — jumlah anak naik lagi menjadi 3.
  5. Baris 27–28 (innerHTML = ""): menugaskan string kosong membuang seluruh isi daftar dalam satu langkah. Properti children.length turun ke 0 — cara tercepat untuk mengosongkan kontainer dengan banyak anak.

removeChild: Cara Lama yang Masih Ditemui #

remove() adalah cara modern (elemen menghapus dirinya sendiri), sedangkan removeChild(node) adalah cara lama yang masih dijumpai di banyak kode dan dokumentasi lama. Dua perbedaan praktisnya:

  • removeChild dipanggil pada induk dan butuh referensi ke node yang dilepas; remove cukup dipanggil dari node yang akan lepas.
  • removeChild mengembalikan node yang dilepas — perilaku yang berguna ketika node itu ingin dipindahkan, bukan sekadar dikeluarkan dari halaman.

Keduanya setara dalam hal hasil: node lepas dari pohon dokumen. Untuk kode baru, remove() hampir selalu lebih nyaman dibaca.

Menghapus dalam Loop: Koleksi Live Menipu #

Perbedaan live vs static dari chapter Selecting Elements menemukan panggungnya di sini. Perhatikan daftar berikut dengan tiga item .hapus yang ingin dibuang semua:

html Daftar yang ingin dibersihkan
<ul id="utama">
  <li class="hapus">A</li>
  <li class="hapus">B</li>
  <li>Tetap</li>
  <li class="hapus">C</li>
</ul>

Cara pertama terlihat masuk akal — jajal koleksi live, hapus yang cocok — tetapi hasilnya salah:

js Dua cara menghapus semua item .hapus
const hidup = document.getElementsByTagName("li");
for (let i = 0; i < hidup.length; i++) {
  if (hidup[i].classList.contains("hapus")) {
    hidup[i].remove();
  }
}
console.log(hidup.length); // 2 — satu .hapus masih tersisa!

const statis = document.querySelectorAll("li.hapus");
statis.forEach((li) => li.remove());
console.log(document.querySelectorAll("li.hapus").length); // 0

Output yang Dihasilkan (console browser):

text
2
0

Keluaran dicatat dari eksekusi nyata di lingkungan jsdom dan identik dengan console browser. Mari kita bedah:

  1. Baris 2–7 (loop di atas koleksi live): setiap remove() menggeser isi koleksi live. Setelah A dilepas, B merangkak naik ke indeks 0 — tepat indeks yang sudah dilewati — sehingga giliran berikutnya memeriksa Tetap, bukan B. Satu item lolos dari penyaringan.
  2. Baris 8 (hidup.length): angkanya 2, bukan 1 — daftar masih berisi B yang lolos dan Tetap yang memang harus tinggal. Tidak ada pesan galat apa pun; bug semacam ini hanya terlihat dari hasilnya.
  3. Baris 10–12 (versi aman): querySelectorAll mencuplik daftar target sebelum penghapusan dimulai, sehingga pergeseran koleksi tidak mungkin terjadi. Setelah loop, hitungan li.hapus benar-benar 0.

Aturannya bisa dirangkum satu kalimat: jangan menghapus sambil menjajal koleksi yang ikut berubah. Ambil cuplikan static terlebih dahulu, atau jajal dari belakang ke depan bila memang harus memakai koleksi live.

Mengosongkan Kontainer dengan innerHTML = "" #

Pola kontainer.innerHTML = "" adalah idiom yang lazim sebelum merender ulang daftar dari data terbaru. Satu penugasan membuang semua keturunan sekaligus, seberapa pun dalamnya sarangnya.

Perlu diingat sisi lainnya: pembuangan massal ini tidak pilih-pilih. Segala yang melekat pada anak-anak kontainer ikut pergi — termasuk data yang tersimpan pada atribut maupun class. Bila sebagian isi harus dipertahankan, hapus satu per satu dengan remove(), atau simpan dulu bagian yang dibutuhkan sebelum pengosongan.

Referensi di Variabel Masih Hidup #

Berkas contoh sudah membuktikannya di baris 22–24: node yang dilepas dari dokumen tidak langsung dibersihkan dari memori. Selama masih ada variabel, array, atau object lain yang memegangnya, node itu tetap utuh dan bisa dipasang kembali kapan saja.

Sifat ini dua sisi. Sisi menguntungkan: daftar item yang sedang tidak tampil bisa disimpan di array, lalu dirender ulang tanpa dibuat ulang dari nol. Sisi yang perlu diwaspadai: lupa melepaskan referensi berarti kumpulan node lama tetap tersimpan meski tidak terlihat di halaman — topik yang akan tersambung rapi dengan manajemen memori di section Performance jauh di akhir course.

Dengan membuat, menempel, menghapus, dan menukar, kita sudah menguasai seluruh gerakan dasar manipulasi struktur. Satu pertanyaan besar tersisa: bagaimana melakukan semuanya secara efisien ketika jumlah elemennya bukan empat, melainkan ratusan — itulah topik chapter DOM Fragments.

Berkas Sumber

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

bersihkan-daftar.js

Unduh berkas
javascript
// bersihkan-daftar.js — menghapus dan mengganti elemen

const daftar = document.querySelector("#tugas");

// 1. remove: elemen menghapus dirinya sendiri
daftar.querySelector(".selesai").remove();
console.log(daftar.children.length); // 3

// 2. replaceWith: menukar dengan elemen baru
const pengganti = document.createElement("li");
pengganti.textContent = "Pesan kemasan ramah lingkungan";
daftar.querySelector(".perlu-ubah").replaceWith(pengganti);
console.log(daftar.children[0].textContent);

// 3. removeChild: bentuk lama, butuh induk, mengembalikan node
const lama = daftar.querySelector(".lama");
const kembali = daftar.removeChild(lama);
console.log(kembali === lama);        // true
console.log(daftar.children.length);  // 2

// 4. node terhapus masih hidup di variabel
console.log(kembali.textContent);     // Perbarui harga
daftar.append(kembali);               // bisa dipasang kembali!
console.log(daftar.children.length);  // 3

// 5. mengosongkan seluruh kontainer
daftar.innerHTML = "";
console.log(daftar.children.length);  // 0