← HTML: cấu trúc & semantic

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

Bài này dạy cách nạp và điều phối script, KHÔNG dạy viết JavaScript. Đừng lo nếu chưa hiểu dòng console.log ở trên - mèo con sẽ gặp nó ở khoá JavaScript. Ở đây ta chỉ quan tâm thẻ script đặt ở đâu và tải ra sao.

Đâ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ẻ scriptcuố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ả deferasync đề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:

HTML dựng (parse)script chạythườngtải + chạy (chặn)xong muộn nhấtdeferHTML dựng liền mạchchạy sauđúng thứ tựasyncchạy ngaythứ tự bất kỳdefer và async đều tải song song không chặn; khác nhau ở LÚC chạy
script thường chặn dựng trang; defer chạy sau khi dựng xong (giữ thứ tự); async chạy ngay khi tải xong.
  • 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

Mèo con vừa thấy: cách nạp script đổi hẳn tốc độ trang hiện ra. Đây mới là một mảnh của bức tranh hiệu năng. Bài kế ta gom các thủ thuật còn lại - tải trước, kết nối sớm, hoãn tải - ở bài Tối ưu tốc độ tải trang.

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ự.

Đa số trường hợp chọn defer: an toàn vì giữ thứ tự và chạy sau khi DOM dựng xong (script hay cần đọc/sửa trang). async hợp với script ĐỘC LẬP, không phụ thuộc trang hay script khác - ví dụ kinh điển là mã thống kê (analytics).

Cả hai đều tránh chặn việc dựng phần nội dung phía trên. Nhưng defer còn tải file SONG SONG ngay từ đầu (cuối-body thì tới cuối mới bắt đầu tải), nên defer thường nhanh hơn một chút. Ngày nay đặt script trong head kèm defer là cách gọn và hiện đại.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Mặc định (không defer/async), khi đang dựng trang mà gặp thẻ script, trình duyệt làm gì?

Bài tập về nhà

  1. 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. 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. 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. 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. 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. 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ỳ.