Panel pengumuman di aplikasi Siti harus berganti isi sepanjang hari: sapaan menyebut nama pengunjung, panel stok menampilkan angka terbaru, dan catatan promo berubah tiap kampanye. Struktur halamannya tetap — yang berubah hanya isi. Untuk kebutuhan seperti ini, DOM menyediakan tiga properti yang sering tertukar: textContent, innerHTML, dan innerText.
Ketiganya sama-sama bisa dibaca dan ditulis, tetapi masing-masing bekerja pada level berbeda. Perbedaan itu bukan sekadar selera: salah satu di antaranya mem-parsing HTML dan bisa membuka celah keamanan bila diberi input pengguna. Chapter ini membedah ketiganya sampai kita tahu persis kapan harus memakai yang mana.
textContent: Teks Polos #
textContent mewakili seluruh isi elemen sebagai teks mentah. Membacanya menghasilkan gabungan teks dari semua node di dalam elemen; menulisnya mengganti seluruh isi dengan satu node teks.
// ubah-konten.js — membaca dan mengganti isi elemen
// 1. textContent: teks polos
const sapaan = document.querySelector("#sapaan");
sapaan.textContent = "Halo, Budi!";
console.log(sapaan.textContent);
// 2. innerHTML: markup dari kode kita sendiri
const panel = document.querySelector("#panel");
panel.innerHTML = "<strong>Stok terbatas</strong> — 12 kemasan";
console.log(panel.textContent);
console.log(panel.innerHTML);
// 3. textContent tidak menafsirkan HTML
const catatan = document.querySelector("#catatan");
catatan.textContent = "Diskon <b>50%</b> hari ini";
console.log(catatan.innerHTML);
console.log(catatan.textContent);Output yang Dihasilkan (console browser):
Halo, Budi!
Stok terbatas — 12 kemasan
<strong>Stok terbatas</strong> — 12 kemasan
Diskon <b>50%</b> hari ini
Diskon <b>50%</b> hari iniKeluaran dicatat dari eksekusi nyata di lingkungan jsdom dan identik dengan console browser. Halaman contohnya hanya berisi tiga penampung kosong — <h2 id="sapaan">, <div id="panel">, dan <p id="catatan"> — yang diisi dari kode. Mari kita bedah:
- Baris 4–6 (
textContenttulis lalu baca): penugasan"Halo, Budi!"mengganti isi<h2>dengan satu node teks. Pembacaan balik mengembalikan teks yang sama — tidak ada pemrosesan apa pun di antaranya. - Baris 10 (
innerHTMLdengan markup): string<strong>Stok terbatas</strong> — 12 kemasandi-parsing browser, sehingga di dalam<div>kini ada satu elemen<strong>plus node teks. Konten yang berupa elemen ini bisa diberi gaya oleh CSS, sesuatu yang tidak mungkin lewat teks polos. - Baris 11–12 (
textContentvsinnerHTMLdari elemen yang sama):textContentmengembalikan teks saja —Stok terbatas — 12 kemasan, tanpa tanda<strong>.innerHTMLmengembalikan serialisasi lengkap termasuk tag-nya. Dua jendela berbeda ke isi yang sama. - Baris 15–17 (string HTML via
textContent): string"Diskon <b>50%</b> hari ini"ditulis apa adanya. PembacaaninnerHTMLdi baris 17 membuktikannya: tanda<dan>tersimpan sebagai entitas<dan>, artinya<b>tidak pernah menjadi elemen — hanya deretan karakter pada layar. - Baris 18 (
textContentbaca ulang): karena tidak ada elemen yang terbentuk, teks yang kembali persis sama dengan string yang ditulis.
Ada satu perilaku textContent lagi yang perlu dipahami penuh sebelum memakainya: saat dibaca, ia menjumlahkan teks dari seluruh keturunan; saat ditulis, ia mengganti seluruh isi — termasuk elemen anaknya.
const luar = document.querySelector("#luar");
console.log(luar.textContent); // Teks di dalam
console.log(luar.children.length); // 1
luar.textContent = "Teks baru";
console.log(luar.children.length); // 0 — anak elemen ikut tergantikan
Mekanisme di balik penugasannya: semua node anak dihapus, lalu satu node teks baru dipasang. Karena itulah children.length turun dari 1 ke 0 — elemen <p> tidak tersisa di mana pun. Bila struktur anak harus dipertahankan, ubah isi node anaknya satu per satu, bukan isi induknya sekali jalan.
innerHTML: Mengganti Isi dengan Markup #
innerHTML bekerja pada level markup: saat dibaca, isi elemen diserialisasi menjadi string HTML; saat ditulis, string tersebut di-parsing menjadi node baru. Seluruh isi lama dibuang dan digantikan hasil parse — penugasan satu baris bisa membangun struktur bertingkat:
panel.innerHTML = `
<h3>Menu Hari Ini</h3>
<ul>
<li>Kopi Gayo</li>
<li>Teh Melati</li>
</ul>
`;
console.log(panel.querySelectorAll("li").length); // 2
Kemudahan ini yang membuat innerHTML populer — dan sekaligus menjadi sumber celah keamanan yang paling sering ditemui, seperti dibedah pada dua bagian berikut.
Satu catatan kecil tentang pola baca-lalu-tulis: panel.innerHTML += "<p>baru</p>" terlihat seperti menambah, tetapi sebenarnya membaca seluruh isi, menyambung string, lalu mem-parsing ulang semuanya dari nol. Hasilnya benar, biayanya tidak — seluruh struktur lama dibangun ulang, dan pola semacam ini justru mendorong penggabungan string dari berbagai sumber, termasuk yang tidak tepercaya. Untuk menambah, insertAdjacentHTML (chapter Creating Elements) atau node baru yang ditempel dengan append bekerja tanpa membongkar isi lama.
Perbedaan yang Menentukan: Parse vs Tulis Apa Adanya #
Mekanisme keduanya bisa diringkas dalam satu kalimat: innerHTML menafsirkan string sebagai HTML, sedangkan textContent menuliskan string sebagai teks. Apa pun yang berada di dalam string innerHTML berpeluang menjadi node sungguhan — elemen, atribut, bahkan atribut penangan event.
Sudah kita lihat bukti arah amannya pada baris 15–17 berkas contoh: string berisi <b> yang ditulis lewat textContent tersimpan sebagai teks, bukan elemen. Arah sebaliknya — string yang sama ditulis lewat innerHTML — akan benar-benar menciptakan elemen <b>. Bila string itu berasal dari kode kita sendiri, tidak ada masalah. Bila string itu berisi ketikan pengguna, kita telah memberi pengguna kendali penuh untuk menyuntik markup ke halaman.
innerText: Singkat Saja #
Properti ketiga, innerText, kira-kira mirip textContent dengan satu perbedaan besar: innerText sadar-tampilan (render-aware). Ia mengembalikan teks “seperti yang dilihat manusia” — bagian yang disembunyikan CSS (misalnya display: none) tidak ikut, dan pindah baris mengikuti tata letak yang sesungguhnya di layar.
Perilaku semacam itu menuntut mesin layout, sesuatu yang tidak dimiliki lingkungan pengujian terminal — karena itu innerText tidak kita uji di jsdom dan contoh di chapter ini sengaja tidak memakainya. Kepada textContent (cepat, tanpa perhitungan layout) dan innerHTML (bila markup memang diperlukan), kebutuhan sehari-hari sudah tercakup hampir seluruhnya. Rujuk MDN bila suatu saat benar-benar membutuhkan innerText.
Panduan Memilih #
| Kebutuhan | Pilihan | Alasan |
|---|---|---|
| Menampilkan atau membaca teks apa pun | textContent | tanpa parsing, paling aman dan cepat |
| Membangun markup dari string kode sendiri | innerHTML | satu baris untuk struktur bertingkat |
| String berisi input pengguna | textContent | input tetap teks; markup tak pernah terbentuk |
| Markup dinamis + data pengguna | escapeHtml() lalu innerHTML | markup dari kode, data dari pengguna tetap teks |
Baris terakhir tabel adalah bentuk paling sering dibutuhkan aplikasi nyata, dan cara mengisi kolom escapeHtml() itu adalah topik penuh chapter Safe DOM Manipulation — penutup section DOM ini.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ubah-konten.js
Unduh berkas// ubah-konten.js — membaca dan mengganti isi elemen
// 1. textContent: teks polos
const sapaan = document.querySelector("#sapaan");
sapaan.textContent = "Halo, Budi!";
console.log(sapaan.textContent);
// 2. innerHTML: markup dari kode kita sendiri
const panel = document.querySelector("#panel");
panel.innerHTML = "<strong>Stok terbatas</strong> — 12 kemasan";
console.log(panel.textContent);
console.log(panel.innerHTML);
// 3. textContent tidak menafsirkan HTML
const catatan = document.querySelector("#catatan");
catatan.textContent = "Diskon <b>50%</b> hari ini";
console.log(catatan.innerHTML);
console.log(catatan.textContent);