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):
3
Pesan kemasan ramah lingkungan
true
2
Perbarui harga
3
0Keluaran dicatat dari eksekusi nyata di lingkungan jsdom dan identik dengan console browser. Mari kita bedah:
- Baris 6–7 (
remove): methodremove()dipanggil pada elemen itu sendiri —daftar.querySelector(".selesai")langsung menghapus dirinya dari induknya. Tidak perlu pegangan ke induk; jumlah anakdaftarturun dari 4 menjadi3. - Baris 10–13 (
replaceWith): elemen baru dibuat dengancreateElement, lalu.replaceWith(pengganti)menukar elemen.perlu-ubahpada posisinya. Pembacaandaftar.children[0]mengonfirmasi slot pertama kini diisi teks pengganti — posisi tetap, isinya berganti. - Baris 16–19 (
removeChild): cara lama ini bekerja dari sisi induk:daftar.removeChild(lama). Method ini mengembalikan node yang baru saja dilepas —kembali === lamabernilaitrue— dan jumlah anak kini2. - Baris 22–24 (node lepas masih utuh): node yang dipegang variabel
kembalimasih lengkap dengan isinya (Perbarui harga). Penugasandaftar.append(kembali)memasangnya kembali ke halaman — jumlah anak naik lagi menjadi3. - Baris 27–28 (
innerHTML = ""): menugaskan string kosong membuang seluruh isidaftardalam satu langkah. Propertichildren.lengthturun ke0— 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:
removeChilddipanggil pada induk dan butuh referensi ke node yang dilepas;removecukup dipanggil dari node yang akan lepas.removeChildmengembalikan 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:
<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:
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):
2
0Keluaran dicatat dari eksekusi nyata di lingkungan jsdom dan identik dengan console browser. Mari kita bedah:
- Baris 2–7 (loop di atas koleksi live): setiap
remove()menggeser isi koleksi live. SetelahAdilepas,Bmerangkak naik ke indeks0— tepat indeks yang sudah dilewati — sehingga giliran berikutnya memeriksaTetap, bukanB. Satu item lolos dari penyaringan. - Baris 8 (
hidup.length): angkanya2, bukan1— daftar masih berisiByang lolos danTetapyang memang harus tinggal. Tidak ada pesan galat apa pun; bug semacam ini hanya terlihat dari hasilnya. - Baris 10–12 (versi aman):
querySelectorAllmencuplik daftar target sebelum penghapusan dimulai, sehingga pergeseran koleksi tidak mungkin terjadi. Setelah loop, hitunganli.hapusbenar-benar0.
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// 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