Bài 9 · Vận dụng · 20 phút
Thẻ script: defer & async
Biên soạn bởi Nguyễn Anh Tuấn
Nhúng JavaScript bằng thẻ script: vì sao script mặc định chặn dựng trang (render-blocking), và cách defer & async tải song song không chặn để trang mở nhanh.
Suốt khoá, mèo con dựng phần cấu trúc bằng HTML. Phần tương tác (bấm nút, gọi dữ liệu…) là việc của JavaScript - học ở khoá JavaScript cơ bản. Nhưng chỉ riêng cách đưa JavaScript vào trang đã là chuyện của HTML, và nó ảnh hưởng lớn tới tốc độ. Cửa ngõ đó là thẻ <script>.
Có hai cách: trỏ tới một file ngoài bằng src, hoặc viết thẳng mã vào giữa hai thẻ. Trang thật gần như luôn dùng file ngoài cho gọn và dễ dùng lại.
script.html · hai cách nhúng (mở .html bằng trình duyệt; logic JS học ở khoá riêng)
<!-- nạp từ file ngoài -->
<script src="app.js"></script>
<!-- viết thẳng vào trang -->
<script>
console.log('Xin chào từ mèo con');
</script> Trung thực
Đây là điều người mới hay bất ngờ. Trình duyệt đọc HTML từ trên xuống để dựng trang. Khi gặp một thẻ script thường, nó phải DỪNG lại: tải file script về, chạy xong, rồi mới đọc tiếp phần HTML còn lại. Người ta gọi đây là render-blocking (chặn dựng trang).
Vì sao trình duyệt phải chờ? Vì script có thể thay đổi trang ngay tại chỗ đó, nên nó dừng cho chắc. Hệ quả: một script nặng đặt ở đầu trang khiến cả phần nội dung phía dưới lâu hiện - người dùng nhìn màn hình trắng.
- ▸Trình duyệt dựng trang bằng cách đọc HTML từ trên xuống.
- ▸Gặp script thường, nó dừng dựng để tải + chạy script (render-blocking).
- ▸Script nặng đặt trên đầu làm nội dung phía dưới lâu hiện.
Mẹo lâu đời để né việc chặn: đặt thẻ script ở cuối body, ngay trước </body>. Khi đó trình duyệt đã dựng xong gần hết nội dung rồi mới gặp script, nên người dùng thấy trang sớm.
cuoi-body.html · script đặt cuối, sau khi nội dung đã dựng
<body>
<h1>Trang của mèo con</h1>
<p>Nội dung hiện ra trước...</p>
<script src="app.js"></script>
</body> Cách này vẫn chạy tốt và bạn sẽ còn gặp nhiều. Nhưng nó có điểm yếu nhỏ: file script tới tận cuối mới bắt đầu được tải. Phần sau sẽ có cách vừa không chặn, vừa tải sớm song song.
Thêm một thuộc tính vào thẻ script là giải quyết gọn. Cả defer và async đều cho trình duyệt tải file song song mà KHÔNG chặn việc dựng trang. Chúng chỉ khác nhau ở lúc chạy:
- ▸defer: tải song song, CHỜ trang dựng xong mới chạy, và chạy đúng THỨ TỰ khai báo.
- ▸async: tải song song, chạy NGAY khi file tải xong - ai xong trước chạy trước, thứ tự bất kỳ.
- ▸Cả hai đều không chặn dựng trang, khác script thường.
defer-async.html · hai thuộc tính, đặt ngay trong head
<head>
<script src="app.js" defer></script>
<script src="thong-ke.js" async></script>
</head> Quy tắc kinh nghiệm gọn: mặc định chọn defer. Nó an toàn vì giữ thứ tự và chỉ chạy khi trang đã dựng xong - phần lớn script cần đọc hoặc sửa nội dung trang nên điều này quan trọng.
Dùng async khi script độc lập: không phụ thuộc trang, không phụ thuộc script khác. Ví dụ kinh điển là mã thống kê lượt xem (analytics) - chạy lúc nào cũng được, thứ tự không quan trọng.
- ▸Mặc định: defer (giữ thứ tự, chạy sau khi DOM dựng xong).
- ▸async: cho script độc lập như analytics - thứ tự không quan trọng.
- ▸Nhiều script phụ thuộc nhau ⇒ defer, để chúng chạy đúng thứ tự.
Bước tiếp theo
Câu hỏi thường gặp
Vì thẻ script là HTML, và CÁCH nạp nó ảnh hưởng trực tiếp tới tốc độ dựng trang - một việc của người viết HTML. Bài này chỉ dạy cách đặt và điều phối tải script (defer/async), không dạy viết logic JavaScript; phần đó có khoá JavaScript riêng.
Trình duyệt đọc HTML từ trên xuống để dựng trang. Khi gặp một thẻ script thường, nó phải DỪNG việc dựng, tải file script về rồi chạy xong mới đọc tiếp. Lý do: script có thể thay đổi trang ngay tại chỗ đó, nên trình duyệt chờ cho chắc. Hệ quả: script nặng đặt trên đầu làm trang lâu hiện.
Cả hai đều tải file script SONG SONG (không chặn dựng trang). Khác ở LÚC chạy: defer chờ trang dựng xong mới chạy, và chạy các script ĐÚNG THỨ TỰ khai báo; async chạy NGAY khi file tải xong, ai xong trước chạy trước, không đảm bảo thứ tự.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Mặc định (không defer/async), khi đang dựng trang mà gặp thẻ script, trình duyệt làm gì?
- 1
Nhúng một script ngoài
Viết thẻ script nạp file "app.js" đặt cùng thư mục trang.
✅ Hoàn thành khi: Có <script src="app.js"></script> đúng cú pháp (thẻ script có cả mở và đóng).
- 2
Vì sao trang trắng lâu?
Một trang đặt <script src="nang.js"> (file nặng) ngay đầu body, không defer/async. Giải thích vì sao nội dung lâu hiện.
✅ Hoàn thành khi: Nêu: script thường chặn dựng trang; trình duyệt dừng để tải+chạy nang.js xong mới dựng tiếp phần dưới.
- 3
Thêm defer
Sửa thẻ script trên để nó tải song song và chạy sau khi trang dựng xong, giữ đúng thứ tự.
✅ Hoàn thành khi: Thêm thuộc tính defer: <script src="nang.js" defer></script> (thường đặt trong head).
- 4
defer hay async?
Chọn defer hay async: (a) script vẽ biểu đồ cần đọc dữ liệu trong trang; (b) mã thống kê lượt xem, chạy độc lập.
✅ Hoàn thành khi: (a) defer (phụ thuộc DOM, cần thứ tự); (b) async (độc lập, chạy lúc nào cũng được).
- 5
Hai script đúng thứ tự
Có thu-vien.js rồi mới tới dung-thu-vien.js (phụ thuộc cái trước). Nên dùng defer hay async, vì sao?
✅ Hoàn thành khi: Dùng defer cho cả hai: defer giữ đúng thứ tự khai báo; async có thể chạy lộn, dung-thu-vien.js chạy trước thì lỗi.
- 6
Một câu cho mèo con
Giải thích bằng lời mèo con: defer và async giống và khác nhau ở đâu, trong 1-2 câu.
✅ Hoàn thành khi: Nêu: cả hai tải song song không chặn trang; defer chạy sau và giữ thứ tự, async chạy ngay khi tải xong, thứ tự bất kỳ.