HTML yang kita tulis sampai chapter ini hidupnya statis: teks dan gambar tampil, lalu tidak terjadi apa-apa lagi. JavaScript adalah cara memberi halaman itu perilaku — memvalidasi isian, menghitung total, merespons klik. Pertanyaan teknisnya satu: bagaimana kode JavaScript masuk ke dalam halaman HTML? Jawabannya ada pada satu tag: <script>.

Tag <script> terlihat sederhana, tetapi letaknya di dalam dokumen menentukan dua hal penting: kapan kode dieksekusi dan elemen mana yang bisa dibacanya. Kedua konsekuensi itu kita amati lewat contoh yang bisa dijalankan.

Script Inline: Pertemuan Pertama #

JavaScript bisa ditulis langsung di dalam HTML, di antara tag <script> dan </script>. Browser membaca dokumen dari atas ke bawah; begitu menyentuh tag <script>, parser langsung menjalankan isinya:

html halaman-uji.html
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <title>Halaman Uji</title>
</head>
<body>
  <p>Halo, ini halaman uji.</p>

  <script>
    console.log("Halo dari tag script!");
  </script>
</body>
</html>

Output yang Dihasilkan (pada tab Console browser):

text
Halo dari tag script!

Cara menulis di antara tag <script> dan </script> seperti ini disebut script inline. Satu halaman boleh memuat beberapa tag <script>; semuanya dieksekusi berurutan dari atas ke bawah, satu per satu. Kelebihan script inline adalah kesederhanaannya: satu berkas saja, tidak ada yang perlu disambungkan. Kita pakai bentuk ini untuk memahami mekanismenya, lalu chapter berikutnya memindahkan kode ke berkas tersendiri untuk halaman yang sesungguhnya.

Banyak Script dalam Satu Halaman #

Dua tag <script> dalam satu halaman berjalan seperti antrean: tag pertama dieksekusi sampai tuntas, baru tag kedua mendapat giliran:

html dua-script.html
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <title>Dua Script</title>
</head>
<body>
  <script>
    console.log("Script pertama berjalan");
  </script>
  <script>
    console.log("Script kedua berjalan");
  </script>
</body>
</html>

Output yang Dihasilkan (pada tab Console browser):

text
Script pertama berjalan
Script kedua berjalan

Urutan ini tidak bergantung pada ukuran kode — tag yang lebih dulu ditulis selalu lebih dulu dieksekusi. Aturan yang sama nanti berlaku untuk beberapa script eksternal, dan justru di situ urutan ini menjadi penting: script kedua sering membutuhkan hasil kerja script pertama.

Mekanisme di Balik Layar: Parser Berhenti #

Untuk memahami mengapa posisi tag penting, kita perlu tahu cara browser membangun halaman. Parser HTML membaca dokumen dari atas ke bawah dan menyusun pohon DOM — representasi struktur halaman di memori. Saat parser menemui tag <script> tanpa atribut src, parser berhenti: isinya dieksekusi sampai tuntas, dan hanya setelah itu pembacaan dokumen dilanjutkan.

Karakter ini disebut blocking — script inline menghalangi pembacaan dokumen. Konsekuensinya konkret: semua elemen yang ditulis setelah tag <script> belum menjadi bagian DOM saat script itu berjalan.

Perlu ditegaskan bahwa DOM dibangun secara bertahap. Elemen yang sudah dilalui parser sudah berada di pohon DOM, sedangkan elemen di bawahnya belum. Itulah sebabnya script yang berjalan setelah sebuah elemen selalu bisa membacanya, dan script yang berjalan sebelum elemen itu selalu mendapati pencariannya kosong. Sifat deterministik inilah yang kita andalkan sebagai solusi — bukan keberuntungan timing.

Jebakan Klasik: Script di head Mencari Elemen yang Belum Ada #

Kebiasaan yang wajar bagi pemula adalah menaruh semua kode di <head> supaya rapi. Mari kita lihat apa yang terjadi:

html halaman-gagal.html
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <title>Halaman Gagal</title>
  <script>
    const judul = document.getElementById("sambutan");
    console.log(judul);
    console.log("Isi judul:", judul.textContent);
  </script>
</head>
<body>
  <h1 id="sambutan">Toko Kita</h1>
</body>
</html>

Output yang Dihasilkan (pada tab Console browser):

text
null
Uncaught TypeError: Cannot read properties of null (reading 'textContent')

Urutan kejadiannya bisa kita rekonstruksi:

  1. Parser membaca dokumen dan menemui tag <script> di <head> — baris 6, sebelum <body> sempat dibaca.
  2. document.getElementById("sambutan") mencari elemen ber-id sambutan. Karena <body> belum menjadi bagian DOM, pencarian mengembalikan null — tanda bahwa tidak ada object yang ditemukan.
  3. Baris console.log(judul) mencetak null, lalu baris berikutnya mencoba membaca properti textContent dari nilai null.
  4. Membaca properti dari null adalah galat, dan di situlah TypeError muncul: tidak ada properti yang bisa dibaca dari nilai yang tidak ada.

Nilai null dan jenis-jenis galat dibedah tuntas di section Values dan Types serta Error Handling. Untuk sekarang, ingat satu pola: null di Console hampir selalu berarti “elemen yang dicari belum ada saat script berjalan”.

Pola galat ini mudah dikenali saat debug: null tercetak dulu, diikuti TypeError pada baris yang membaca properti. Ketika keduanya muncul berurutan dari script yang sama, pertanyaan pertama yang layak diajukan bukan “kenapa propertinya tidak ada”, melainkan “apa script ini berjalan sebelum elemennya ter-parse”. Periksa posisi tag <script>-nya terlebih dahulu.

Solusi: Letakkan Script di Akhir Body #

Perbaikan paling langsung: pindahkan tag <script> ke posisi setelah semua elemen yang dibacanya — di akhir <body>. Berdasarkan mekanisme parser di atas, saat parser tiba di baris script itu, seluruh elemen sudah berada di DOM:

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <title>Toko Budi</title>
</head>
<body>
  <h1 id="sambutan">Selamat datang di Toko Budi</h1>

  <script>
    const judul = document.getElementById("sambutan");
    console.log("Judul halaman:", judul.textContent);
    console.log("Skrip dijalankan setelah elemen tersedia");
  </script>
</body>
</html>

Output yang Dihasilkan (pada tab Console browser):

text
Judul halaman: Selamat datang di Toko Budi
Skrip dijalankan setelah elemen tersedia

Mari kita telaah baris demi baris:

  1. Baris 8 (<h1 id="sambutan">...</h1>): elemen yang akan dibaca script; nilai atribut id menjadi alamat pencariannya.
  2. Baris 10 (<script>): parser mencapai script setelah <h1> selesai dimasukkan ke DOM, sehingga pencarian di bawahnya pasti berhasil.
  3. Baris 11 (const judul = document.getElementById("sambutan")): kali ini pencarian menemukan elemennya; variable judul menunjuk ke <h1> tersebut.
  4. Baris 12 (console.log("Judul halaman:", judul.textContent)): properti textContent berisi teks di dalam elemen, dan teks itu tercetak lengkap.
  5. Baris 13 (console.log("Skrip dijalankan setelah elemen tersedia")): pesan kedua menyegarkan kenapa skrip ini berhasil — urutan penempatannya yang benar.

Posisi Tag: Ringkasan Perilaku #

Tabel berikut merangkum perilaku setiap penempatan yang sudah dan akan kita bahas:

Posisi tag <script>KonsekuensiCocok untuk
Di <head>, tanpa atributParser berhenti sampai script selesai; elemen <body> belum terlihatScript yang tidak membaca elemen halaman
Di akhir <body>Seluruh elemen sudah ada di DOM saat script berjalanPola baku untuk pemula — pakai ini dulu
Di <head> dengan deferUnduhan paralel dengan parsing, eksekusi setelah dokumen siapSolusi modern — dibahas di chapter defer dan async

Dua baris pertama tabel sudah bisa kita pakai hari ini; baris ketiga baru masuk akal setelah kode berpindah ke berkas eksternal. Untuk sisa section ini, konvensi kita tetap: script di akhir <body>, tepat sebelum penutup </body>, sesuai pola halaman-akhir-body.html.

Dengan satu tag <script> kita sudah bisa menyematkan perilaku ke halaman. Satu langkah lagi untuk melengkapi lingkungan browser: memindahkan kode dari dalam HTML ke berkas tersendiri.