Soal 1Di script tanpa defer yang dimuat di head, apa yang dikembalikan document.querySelector("#total")?
Cara JavaScript Berjalan di Browser
Membahas cara browser membuat DOM dari HTML, urutan link dan script diproses, atribut defer, Console DevTools, dan cara mengecek hasil sebagai teks di console latihan.
Kode JavaScript yang mengubah teks di halaman bisa jalan atau tidak, tergantung di mana kode itu dimuat. Saat tidak jalan pun, halaman tidak menampilkan error apa-apa, jadi kamu tidak bisa membedakan apakah kodenya yang salah atau urutan eksekusinya yang bermasalah.
Artikel ini membahas urutan browser memuat HTML dan menjalankan JavaScript.
Contoh di Artikel Ini Dibuka sebagai Halaman HTML
Console latihan tidak bisa meniru urutan pemuatan HTML, jadi artikel ini tidak dilengkapi latihan. Setiap contoh menunjukkan apa yang terjadi kalau kamu menaruh index.html dan app.js di folder yang sama lalu membuka index.html di Chrome. Hasilnya ditulis sebagai komentar di ujung baris. Cara mengecek di console dibahas di bagian terakhir.
Menyusun Element Bersarang dari HTML — DOM Tree
Anggap kamu ingin app.js mengubah teks "Menghitung…" di halaman konfirmasi pesanan menjadi "3.300 yen". Yang diubah JavaScript di sini bukan teks di dalam file index.html, melainkan data lain yang disusun browser setelah membaca HTML tersebut.
Browser mengubah setiap element HTML (bagian halaman yang dibatasi tag, seperti <p>…</p>) menjadi object, dan kumpulan hubungan parent-child di antara object itu disebut DOM (Document Object Model, disebut juga DOM tree; diakses lewat object document).
Pengaturan yang ditulis di dalam tag, seperti id="total" dan defer, disebut atribut. Value atribut id harus unik: tidak boleh ada element lain di halaman yang memakai id yang sama.
<!-- ---- index.html (halaman konfirmasi pesanan) ---- -->
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Konfirmasi Pesanan</title>
<link rel="stylesheet" href="style.css"> <!-- CSS yang mengatur tampilan -->
</head>
<body>
<h1>Konfirmasi Pesanan</h1>
<ul id="items"> <!-- punya 2 child li -->
<li>Mug × 2</li>
<li>Tatakan gelas × 1</li>
</ul>
<p id="total">Menghitung…</p> <!-- element yang ingin diubah app.js -->
</body>
</html>
meta— encoding karakternya UTF-8title— isinya "Konfirmasi Pesanan"link— menunjuk ke style.css
- Isinya "Konfirmasi Pesanan"
li— isinya "Mug × 2"li— isinya "Tatakan gelas × 1"
- Isinya "Menghitung…"
- Element yang ingin diubah app.js
app.js mengambil element p dengan document.querySelector("#total") (mencari element yang punya id sesuai tulisan setelah #, dan mengembalikan null kalau tidak ada), lalu mengubah textContent-nya (teks di dalam element). Element p adalah child dari body, jadi element itu hanya bisa ditemukan kalau body sudah masuk ke DOM.
Membaca HTML dari Atas — Urutan link dan script
Misalkan app.js, yang memperbarui total, kamu muat di <head> bersama CSS. index.html jelas berisi <p id="total">, tapi saat halaman dibuka, tulisannya tetap "Menghitung…" dan perubahan dari app.js tidak terlihat.
Selama parsing HTML (proses browser membaca HTML dari awal dan menambahkan setiap element yang ditemukannya ke DOM secara berurutan), kalau browser menemukan <link>, parsing tetap berjalan sambil CSS diambil. Kalau yang ditemukan <script src>, parsing dihentikan, JavaScript diambil dan dijalankan sampai selesai, baru sisanya dibaca.
// ---- index.html (memuat app.js di head) ----
// <head>
// <link rel="stylesheet" href="style.css"> ← parsing tetap berjalan
// <script src="app.js"></script> ← parsing berhenti untuk menjalankannya
// </head>
// <body>
// <p id="total">Menghitung…</p> ← masuk ke DOM setelah app.js
// </body>
// ---- app.js ----
const total = document.querySelector("#total"); // null (belum ada di DOM)
total.textContent = "3.300 yen";
// TypeError: Cannot set properties of null (setting 'textContent')
Perubahannya tidak berhasil karena element p belum ada di DOM saat app.js dijalankan. Di bagian berikutnya, dua baris di app.js tidak diubah; yang digeser hanya waktu eksekusinya, yaitu setelah DOM lengkap.
Menunggu DOM Lengkap Sebelum Dijalankan — Atribut defer
Kalau <script> kamu pindahkan ke tepat sebelum </body>, script dijalankan setelah element p dibaca, jadi #total ditemukan. Tapi dengan cara ini, jalan atau tidaknya kode bergantung pada posisi tag, dan kalau nanti baris pemuatannya dipindah lagi ke <head> bersama CSS, null yang sama akan muncul lagi.
Atribut defer (pengaturan <script> yang membuat JavaScript diambil tanpa menghentikan parsing HTML, lalu dijalankan setelah DOM lengkap) ditulis sebagai <script defer src="app.js"></script>, dan bisa ditaruh di <head> seperti <link> untuk CSS.
// ---- index.html (memuat di head dengan defer) ----
// <head>
// <link rel="stylesheet" href="style.css">
// <script defer src="app.js"></script> ← mulai diambil, parsing tetap berjalan
// </head>
// <body>
// <p id="total">Menghitung…</p> ← app.js baru dijalankan setelah semua sampai </html> dibaca
// </body>
// ---- app.js (2 baris yang sama seperti sebelumnya + 1 baris untuk mengecek) ----
const total = document.querySelector("#total"); // element p
total.textContent = "3.300 yen"; // teks di halaman berubah
console.log(total.textContent); // 3.300 yen
Kalau ada beberapa script dengan defer, script-script itu dijalankan sesuai urutan penulisannya di HTML, bukan urutan selesai diambil. Tabel di bawah merangkum, untuk tiap cara menulis script, kapan app.js dijalankan dan apakah #total ditemukan.
| Cara menulis script | Kapan app.js jalan | #total |
|---|---|---|
| <script src="app.js"> di head | Langsung, parsing berhenti | Tidak ada (null) |
| <script src="app.js"> sebelum </body> | Setelah p dibaca | Ditemukan |
| <script defer src="app.js"> di head | Setelah DOM lengkap | Ditemukan |
| Kode inline di <script defer> di head | Langsung (tanpa src, defer diabaikan) | Tidak ada (null) |
| <script type="module" src="app.js"> di head | Setelah DOM, meski tanpa defer | Ditemukan (tidak jalan di file://) |
Mencari Baris yang Error dan Memeriksa DOM — DevTools
Di halaman sebelum defer ditambahkan, app.js melempar TypeError, tapi tidak ada apa pun yang muncul di halaman. Tanpa tahu di file mana dan baris ke berapa error-nya terjadi, kamu tidak bisa melacak kenapa halaman terus menampilkan "Menghitung…".
DevTools (panel bawaan browser untuk memeriksa DOM dan error di sebuah halaman) punya tab Console, yang di Chrome bisa dibuka dengan Ctrl + Shift + J di Windows atau Command + Option + J di Mac. Di sana muncul output console.log, beserta error yang terjadi dan nama file serta nomor barisnya.
// ---- Error yang muncul di tab Console (klik app.js:2 di sebelah kanan untuk membuka baris itu) ----
// Uncaught TypeError: Cannot set properties of null (setting 'textContent')
// at app.js:2:19 ← terjadi di baris 2, kolom 19
// ---- Setelah halaman dimuat, ketik ini setelah tanda > di Console lalu tekan Enter ----
document.querySelector("#total"); // <p id="total">Menghitung…</p>
document.querySelector("#total").textContent; // 'Menghitung…'
// ---- Baris yang ditambahkan setelah baris 1 di app.js (jangan diketik di Console) ----
console.log("total saat dijalankan:", total); // total saat dijalankan: null
Ekspresi yang kamu ketik di Console dijalankan terhadap DOM saat itu juga. Untuk mengecek value variabel di dalam app.js, tambahkan console.log sebelum baris yang kamu curigai, supaya value-nya saat kode berjalan tampil di tab Console.
Mengecek Hasil sebagai Teks — innerHTML dan outerHTML
Di latihan pada artikel-artikel berikutnya, kamu akan mencari, mengubah, dan menambahkan element. Masalahnya, console latihan tidak punya halaman yang terlihat, dan selama halaman belum dimuat ulang, semua latihan memakai satu halaman tersembunyi yang sama. Akibatnya, setiap eksekusi dimulai dengan element dari eksekusi sebelumnya yang masih tertinggal.
Kalau kamu meng-assign string ke innerHTML (property untuk membaca dan menulis isi element sebagai string HTML), seluruh isinya diganti. Setiap latihan dimulai dengan meng-assign innerHTML milik document.body (element body) untuk menghapus element lama, lalu hasilnya dicek dengan menampilkan outerHTML (string HTML yang ikut memuat tag element itu sendiri).
// ① Ganti seluruh isi body (element sisa eksekusi sebelumnya terhapus di sini)
document.body.innerHTML = `<p id="total">Menghitung…</p>`;
// ② Cari element-nya, lalu ubah teks di dalamnya
const total = document.querySelector("#total");
total.textContent = "3.300 yen";
// ③ Halamannya tidak terlihat, jadi tampilkan hasilnya sebagai string
console.log(total.textContent); // 3.300 yen
console.log(total.outerHTML); // <p id="total">3.300 yen</p>
console.log(document.body.innerHTML); // <p id="total">3.300 yen</p>
- Yang kembali hanya output
console.log, berupa teks
- Isi
document.body— tetap ada sampai eksekusi berikutnya
const total— hanya bisa dipakai di eksekusi ini- Meng-assign p ke
document.body.innerHTML
const total— dibuat sebagai variabel baru, terpisah dari sebelumnya- Assignment di awal menghapus p dari eksekusi pertama
Kode di console dijalankan di dalam function baru setiap kali, jadi const total dibuat sebagai variabel yang terpisah dari eksekusi sebelumnya dan tidak memunculkan error deklarasi ulang. Kode itu juga dijalankan setelah halaman selesai dimuat, jadi kamu tidak perlu memikirkan defer.
Element yang Langsung Di-log Tampil sebagai {}
Console latihan tidak punya halaman untuk ditampilkan, jadi output diubah menjadi teks sebelum dikembalikan. Kalau element kamu berikan langsung ke console.log, yang muncul hanya {} tanpa isi, sehingga kamu tidak bisa tahu apakah perubahannya berhasil. Untuk mengecek element, tampilkan outerHTML atau textContent-nya.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Dengan <script defer src="app.js">, kapan app.js dijalankan?
Soal 3Kalau document.body.innerHTML = '<p>Menghitung…</p>'; dijalankan dua kali, ada berapa element p?