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>
DOM Tree yang Dibuat dari index.html
Element html — root DOM, diakses lewat document
Element head — info yang tidak tampil
  • meta — encoding karakternya UTF-8
  • title — isinya "Konfirmasi Pesanan"
  • link — menunjuk ke style.css
Element body — bagian yang tampil
Element h1
  • Isinya "Konfirmasi Pesanan"
Element ul — id-nya items
  • li — isinya "Mug × 2"
  • li — isinya "Tatakan gelas × 1"
Element p — id-nya total
  • Isinya "Menghitung…"
  • Element yang ingin diubah app.js
head dan body berdampingan sebagai child dari html, dan li berada di dalam ul. Susunan tag yang bersarang menjadi hubungan parent-child di DOM.

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')
Saat Script Berjalan, p Belum Ada
<link href="style.css">Mengambilfile CSSParsing lanjutke baris berikut<script src="app.js">Parsing berhenti,app.js dijalankan#total belum ada,null dikembalikan<p id="total">Parsing lanjut,p masuk ke DOMBaru masuk, app.jssudah dapat null</html>Parsing selesai,DOM lengkapTetap tampil"Menghitung…"
Parsing berhenti di baris script, dan body baru dibaca setelah app.js selesai dijalankan. Meskipun app.js melempar error, parsing tetap dilanjutkan dan halaman tetap tampil.

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
defer: Diambil Paralel, Dijalankan Terakhir
<script defersrc="app.js">Lanjut membaca,body masuk ke DOMSelesai membacasampai </html>DOM lengkapMulai mengambilapp.jsDiambil sambilparsing lanjutSudah diambil,belum dijalankanapp.js dijalankan,#total ditemukan
app.js diambil sementara parsing berjalan, dan meskipun pengambilannya selesai lebih dulu, eksekusinya tetap menunggu. app.js baru dijalankan setelah DOM lengkap.

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 scriptKapan app.js jalan#total
<script src="app.js"> di headLangsung, parsing berhentiTidak ada (null)
<script src="app.js"> sebelum </body>Setelah p dibacaDitemukan
<script defer src="app.js"> di headSetelah DOM lengkapDitemukan
Kode inline di <script defer> di headLangsung (tanpa src, defer diabaikan)Tidak ada (null)
<script type="module" src="app.js"> di headSetelah DOM, meski tanpa deferDitemukan (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 Sama Mengembalikan p Setelah Dimuat
Saat dimuat,app.js berjalanParsing berhentidi headquerySelectormengembalikan nullTypeErrordi baris 2Diketik di Consolesetelah dimuatParsing selesai,DOM lengkapquerySelectormengembalikan pTeks "Menghitung…"bisa dibaca
Ekspresi yang mengembalikan null di app.js mengembalikan element p di Console. Kode yang jalan di Console belum tentu jalan di app.js.

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>
Halaman Tersembunyi yang Dipakai Bersama
Halaman artikel — console latihan
  • Yang kembali hanya output console.log, berupa teks
Halaman tersembunyi — satu untuk semua latihan
  • Isi document.body — tetap ada sampai eksekusi berikutnya
Eksekusi pertama
  • const total — hanya bisa dipakai di eksekusi ini
  • Meng-assign p ke document.body.innerHTML
Eksekusi kedua
  • const total — dibuat sebagai variabel baru, terpisah dari sebelumnya
  • Assignment di awal menghapus p dari eksekusi pertama
Isi body tetap ada sampai eksekusi berikutnya. Assignment ke innerHTML di awal kode menghapus element yang tertinggal.

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.

QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Di script tanpa defer yang dimuat di head, apa yang dikembalikan document.querySelector("#total")?

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?