JavaScript itu mahal bukan cuma karena ukurannya. Setelah di-download, browser masih harus membaca kodenya, mengubahnya jadi instruksi, lalu menjalankannya, dan sebagian besar kerja itu terjadi di main thread: satu-satunya jalur yang juga dipakai untuk merespons klik dan menggambar layar. Bayangin warung dengan satu koki. Gambar itu kue jadi: tinggal buka bungkus, taruh di etalase. JavaScript itu resep: koki harus membaca dan memahami resepnya dulu, baru memasak. Selama dia masak, pesanan baru antre.
🧪 Tebakan cepat: di HP menengah, 200 KB JavaScript bikin main thread sibuk tanpa jeda selama 400 ms. Kamu klik tombol di ms ke-150. Klikmu nunggu berapa lama sebelum diproses: 0 ms, 50 ms, atau 250 ms? Jawabannya ada di bagian "Main thread cuma satu".
Empat tahap: download, parse, compile, execute
- Download: file dikirim lewat jaringan. Tahap ini gak memakai main thread.
- Parse: browser membaca teks kodenya dan memahami strukturnya.
- Compile: hasil parse diubah jadi instruksi yang bisa dijalankan mesin JavaScript.
- Execute: kodenya dijalankan: membuat variabel, memanggil function, mengubah DOM.
Parse, compile, dan execute butuh kerja CPU, dan sebagian besarnya jalan di main thread. Gambar cuma butuh download lalu decode (mengubah file jadi piksel), dan browser modern bisa decode di luar main thread. Itu sebabnya 200 KB JavaScript jauh lebih mahal daripada 200 KB gambar.
Angka simulasi dari Lab di halaman ini, dengan jaringan 1,6 Mbps (4G lambat, sama seperti Lighthouse):
| Tahap | 200 KB JS, laptop | 200 KB JS, HP menengah | 200 KB gambar, HP menengah |
|---|---|---|---|
| Download | 1.000 ms | 1.000 ms | 1.000 ms |
| Parse dan compile | 20 ms | 80 ms | gak ada |
| Execute | 80 ms | 320 ms | gak ada |
| Decode | gak ada | gak ada | 40 ms |
| Kerja CPU | 100 ms | 400 ms | 40 ms |
Download-nya sama karena jaringannya sama. Yang beda kerja CPU-nya. HP menengah menjalankan JavaScript beberapa kali lebih lambat daripada laptop. Lab memakai 4 kali, sama seperti perlambatan CPU di Lighthouse. Berapa lama execute juga tergantung apa yang dikerjakan kodenya, bukan cuma ukurannya.
Main thread cuma satu
Main thread adalah satu jalur kerja browser untuk menjalankan JavaScript, menghitung layout, menggambar, dan merespons input. Kerjanya dibagi jadi task: satu potong kerja yang harus selesai dulu sebelum browser boleh mengerjakan hal lain. Task yang lebih dari 50 ms disebut long task.
Di HP menengah, 200 KB tadi jadi satu task 400 ms: parse dan compile dari 0 sampai 80 ms, lalu execute dari 80 sampai 400 ms tanpa jeda.
| Waktu | Main thread | Klikmu |
|---|---|---|
| 0 ms | mulai parse dan compile | |
| 80 ms | mulai execute | |
| 150 ms | masih execute | kamu klik, klik masuk antrean |
| 400 ms | task selesai | handler klik mulai jalan |
| 410 ms | kosong | handler selesai (10 ms) |
Klikmu nunggu dari ms ke-150 sampai ms ke-400: 250 ms. Itu jawaban tebakannya. Selama itu tombolnya terasa mati. Dari pelajaran Core Web Vitals kamu sudah kenal INP, yang good-nya maksimal 200 ms. Satu long task seperti ini cukup untuk bikin INP buruk.
Memecah task yang panjang
Total kerjanya gak bisa hilang, tapi bisa dipecah jadi potongan kecil. Di antara potongan, kodenya kasih giliran (yield) ke browser, jadi klik yang antre bisa diproses dulu.
function kasihGiliran() {
return new Promise((resolve) => setTimeout(resolve, 0));
}
async function mulaiAplikasi(produk) {
let mulai = performance.now();
for (const item of produk) {
renderKartu(item);
if (performance.now() - mulai > 25) {
await kasihGiliran(); // klik yang antre diproses di sini
mulai = performance.now();
}
}
}
kasihGiliran()mengembalikan Promise yang selesai lewatsetTimeout(resolve, 0). Sisa loop-nya jadi task baru di belakang antrean, dan input yang sudah menunggu diproses duluan.performance.now()memberi waktu sekarang dalam milidetik. Begitu satu potongan sudah lewat 25 ms, loop berhenti sebentar.awaitcuma boleh dipakai di functionasync, makanyamulaiAplikasidiberiasync.- Browser berbasis Chromium juga punya
scheduler.yield()untuk tujuan yang sama.setTimeoutjalan di semua browser.
Dengan potongan 25 ms, 200 KB di HP menengah jadi begini: task pertama dari 0 sampai 105 ms (compile 80 ms ditambah potongan pertama), lalu potongan 25 ms sampai ms ke-400. Klik di ms ke-150 jatuh di potongan 130 sampai 155 ms, jadi cuma nunggu 5 ms. Total kerjanya tetap sekitar 400 ms, malah sedikit lebih lama, tapi halamannya terasa hidup.
Task pertama masih 105 ms, karena parse dan compile gak bisa kamu pecah sendiri.
Kirim JavaScript lebih sedikit
Cara paling ampuh mengurangi parse, compile, dan execute adalah gak mengirim kodenya sama sekali. Di Lab, 50 KB di HP menengah cuma butuh compile 20 ms, jadi setelah dipecah gak ada long task sama sekali. Caranya ada di pelajaran-pelajaran berikutnya, misalnya code splitting (memuat kode per halaman). Dari pelajaran critical rendering path kamu sudah punya defer, supaya script gak menahan HTML.
⚠️ Jebakan yang sering ketemu
- Cuma melihat ukuran file. 200 KB gambar dan 200 KB JavaScript sama-sama butuh 1 detik untuk download, tapi JavaScript-nya masih butuh 400 ms kerja CPU di HP.
- Mengetes di laptop saja. Di laptop task-nya 100 ms, di HP menengah 400 ms. Tes di HP sungguhan, atau pakai CPU throttling 4x di DevTools.
- Mengira dipecah berarti lebih cepat. Total kerjanya sama. Yang berubah: klik gak perlu nunggu task panjang selesai.
- Lupa
await.kasihGiliran()tanpaawaitgak menghentikan loop, jadi kerjanya tetap satu task panjang.
TL;DR: Biaya JavaScript ada empat: download, parse, compile, dan execute. Tiga yang terakhir butuh kerja CPU, sebagian besar di main thread, sedangkan gambar cuma di-download dan di-decode. Main thread cuma satu: task yang lebih dari 50 ms disebut long task, dan klik yang datang di tengahnya harus nunggu sampai selesai (klik di ms ke-150 pada task 400 ms nunggu 250 ms). HP menengah menjalankan JavaScript beberapa kali lebih lambat daripada laptop, jadi ukur di sana. Pecah kerja panjang jadi potongan kecil dengan await ke setTimeout supaya klik bisa menyela, dan kirim JavaScript lebih sedikit, karena parse dan compile gak bisa kamu pecah.