Dipublikasikan: 31 Desember 2013
JavaScript memungkinkan kita untuk mengubah hampir semua aspek halaman: konten, gaya visual, dan responsnya terhadap interaksi pengguna. Namun, JavaScript juga dapat memblokir konstruksi DOM dan menunda waktu laman dirender. Untuk menghasilkan performa yang optimal, jadikan JavaScript Anda asinkron dan hapus setiap JavaScript yang tidak diperlukan dari jalur rendering penting.
Ringkasan
- JavaScript dapat melakukan kueri dan memodifikasi DOM dan CSSOM.
- Eksekusi JavaScript memblokir di GCLID.
- JavaScript memblokir konstruksi DOM kecuali secara tegas dideklarasikan sebagai asinkron.
JavaScript adalah bahasa dinamis yang berjalan di browser dan memungkinkan kita untuk mengubah hampir setiap aspek dari cara kerja halaman: kita dapat memodifikasi konten dengan menambahkan dan menghapus elemen dari hierarki DOM; kita dapat memodifikasi properti CSSOM setiap elemen; kita dapat menangani input pengguna; dan banyak lagi. Untuk mengilustrasikan hal ini, lihat apa yang terjadi saat contoh "Hello World" sebelumnya diubah untuk menambahkan skrip inline singkat:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
JavaScript memungkinkan kita mengakses DOM dan mengambil referensi ke simpul bentang tersembunyi; simpul ini mungkin tidak terlihat di pohon render, namun masih ada di DOM. Selanjutnya, bila memiliki referensinya, kita bisa mengubah teksnya (lewat .textContent), dan bahkan mengganti properti gaya tampilan yang dihitungnya dari "none" menjadi "inline". Sekarang halaman kita menampilkan "Hello interactive students!".
JavaScript juga memungkinkan kita untuk membuat, menata gaya, menambahkan, dan membuang elemen baru di DOM. Secara teknis, keseluruhan halaman kita bisa berupa satu file JavaScript besar yang membuat dan menata gaya elemen satu per satu. Meskipun itu bisa dilakukan, dalam praktiknya menggunakan HTML dan CSS jauh lebih mudah. Di bagian kedua fungsi JavaScript, kita membuat elemen div yang baru, menetapkan materi teksnya, menggayakannya, dan menambahkannya ke tubuh.
Dengan hal itu, kita telah memodifikasi konten dan gaya CSS dari node DOM yang ada, dan menambahkan seluruh node yang baru ke dokumen. Laman kita tidak akan memenangkan penghargaan desain, tetapi laman menggambarkan kemampuan dan fleksibilitas yang ditawarkan JavaScript kepada kita.
Namun, meskipun memiliki banyak kemampuan, JavaScript menciptakan banyak batasan tambahan terkait cara dan waktu halaman dirender.
Pertama, perhatikan bahwa dalam contoh sebelumnya bahwa skrip sebaris kita ada di dekat bagian bawah laman. Mengapa? Anda harus mencobanya sendiri, tetapi jika kita memindahkan skrip di atas elemen <span>
, Anda akan melihat bahwa skrip akan gagal dan mengeluh bahwa ia tidak dapat menemukan referensi ke elemen <span>
dalam dokumen; yaitu, getElementsByTagName('span')
menampilkan null
. Ini mendemonstrasikan properti penting: skrip kita dieksekusi pada titik penyisipan yang tepat di dalam dokumen. Saat parser HTML menemukan tag skrip, parser akan menjeda proses konstruksi DOM dan menghasilkan kontrol atas mesin JavaScript; setelah mesin JavaScript selesai dijalankan, browser selanjutnya melanjutkan dari tempat terakhir dan meneruskan konstruksi DOM.
Dengan kata lain, blok skrip kita tidak dapat menemukan elemen apa pun nanti di halaman karena belum diproses! Atau, dengan kata lain: mengeksekusi skrip inline akan memblokir konstruksi DOM, yang juga akan menunda render awal.
Properti halus lainnya dalam memasukkan skrip ke dalam laman kita adalah bahwa skrip dapat membaca dan memodifikasi tidak hanya DOM, tetapi juga properti GCLID. Sebenarnya, itulah yang kita lakukan dalam contoh kita ketika kita mengubah properti tampilan elemen span dari none menjadi inline. Hasil akhirnya? Sekarang kita memiliki kondisi race.
Bagaimana jika browser belum selesai mendownload dan mem-build CSSOM saat kita ingin menjalankan skrip? Jawabannya tidak terlalu bagus untuk performa: browser menunda eksekusi skrip dan konstruksi DOM hingga selesai mendownload dan membuat CSSOM.
Singkatnya, JavaScript menimbulkan banyak dependensi baru di antara eksekusi DOM, CSSOM, dan JavaScript. Hal ini dapat menyebabkan penundaan browser secara signifikan dalam pemrosesan dan rendering halaman pada layar:
- Lokasi skrip dalam dokumen sifatnya signifikan.
- Saat browser menemukan tag skrip, konstruksi DOM akan berhenti sementara hingga skrip selesai dieksekusi.
- JavaScript dapat melakukan kueri dan memodifikasi DOM dan CSSOM.
- Eksekusi JavaScript akan berhenti sementara hingga CSSOM siap.
Secara garis besar, "mengoptimalkan jalur rendering penting" mengacu pada pemahaman dan pengoptimalan grafik dependensi antara HTML, CSS, dan JavaScript.
Pemblokiran parser versus JavaScript asinkron
Secara default, eksekusi JavaScript adalah "pemblokiran parser": saat browser menemukan skrip dalam dokumen, browser harus menjeda konstruksi DOM, menyerahkan kontrol ke runtime JavaScript, dan membiarkan skrip dieksekusi sebelum melanjutkan konstruksi DOM. Kita melihat aksinya pada skrip inline dalam contoh kita sebelumnya. Faktanya, skrip inline selalu memblokir parser kecuali jika Anda menulis kode tambahan untuk menunda eksekusinya.
Bagaimana dengan skrip yang disertakan menggunakan tag skrip? Ambil contoh sebelumnya dan ekstrak kode ke dalam file terpisah:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script External</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
app.js
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
Baik kami menggunakan tag <script> maupun cuplikan JavaScript inline, Anda tentu akan mengharapkan keduanya berperilaku dengan cara yang sama. Dalam kedua kasus ini, browser menjeda dan mengeksekusi skrip sebelum dapat memproses bagian dokumen selanjutnya. Namun, untuk file JavaScript eksternal, browser harus dijeda untuk menunggu skrip diambil dari disk, cache, atau server jarak jauh, yang dapat menambah jeda dari puluhan hingga ribuan milidetik ke jalur rendering kritis.
Secara default, semua JavaScript memblokir parser. Karena tidak mengetahui rencana tindakan skrip pada halaman, browser mengasumsikan skenario terburuk dan memblokir parser. Sinyal kepada browser bahwa skrip tidak perlu dieksekusi pada titik yang persis dengan yang direferensikan akan memungkinkan browser melanjutkan konstruksi DOM dan membiarkan skrip dieksekusi bila sudah siap; misalnya, setelah file diambil dari cache atau server jauh.
Untuk mencapainya, atribut async
ditambahkan ke elemen <script>
:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
<title>Critical Path: Script Async</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Menambahkan kata kunci asinkron ke tag skrip akan memberi tahu browser agar tidak memblokir konstruksi DOM saat menunggu skrip tersedia, yang dapat meningkatkan performa secara signifikan.