Trong các trang web hiện đại, các tập lệnh thường “nặng” hơn HTML: kích thước tải xuống của chúng lớn hơn và thời gian xử lý cũng lâu hơn.

Khi trình duyệt tải HTML và gặp thẻ <script>...</script>, trình duyệt không thể tiếp tục tạo DOM. Nó phải thực thi tập lệnh ngay bây giờ. Điều tương tự cũng xảy ra đối với các tập lệnh bên ngoài <script src="..."></script>: trình duyệt phải đợi cho đến khi tập lệnh tải xuống, thực thi nó và chỉ sau khi xử lý phần còn lại của trang.

Cafedev chia sẻ về cách dùng async và defer cùng với các vấn đề liên quan..

Điều đó dẫn đến hai vấn đề quan trọng:

  1. Tập lệnh không thể nhìn thấy các phần tử DOM bên dưới chúng, vì vậy chúng không thể thêm trình xử lý, v.v.
  2. Nếu có một tập lệnh cồng kềnh ở đầu trang, nó sẽ “chặn trang”. Người dùng không thể xem nội dung trang cho đến khi nó tải xuống và chạy:
<p>...content before script...</p>

<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- This isn't visible until the script loads -->
<p>...content after script...</p>

Có một số giải pháp thay thế cho điều đó. Ví dụ, chúng ta có thể đặt một tập lệnh ở cuối trang. Sau đó, nó có thể nhìn thấy các phần tử phía trên nó và nó không chặn nội dung trang hiển thị:

<body>
  ...all content is above the script...

  <script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
</body>

Nhưng giải pháp này còn lâu mới hoàn hảo. Ví dụ: trình duyệt chỉ nhận thấy tập lệnh (và có thể bắt đầu tải xuống) sau khi tải xuống toàn bộ tài liệu HTML. Đối với các tài liệu HTML dài, đó có thể là một sự chậm trễ đáng chú ý.

Những thứ như vậy là vô hình đối với những người sử dụng kết nối rất nhanh, nhưng nhiều người trên thế giới vẫn có tốc độ internet chậm và sử dụng kết nối internet di động không hoàn hảo.

May mắn thay, có hai thuộc tính <script> giải quyết vấn đề cho chúng ta: defer và async.

1. defer(hoãn lại)

Các thuộc tính defer cho trình duyệt mà nó nên tiếp tục làm việc với các trang web, và tải các kịch bản “trong nền, là chạy ngầm”, sau đó chạy kịch bản khi nó tải.




Đây là ví dụ tương tự như trên, nhưng với defer:

<p>...content before script...</p>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- visible immediately -->
<p>...content after script...</p>
  • Tập lệnh defer không bao giờ chặn trang.
  • Các tập lệnh defer luôn thực thi khi DOM sẵn sàng, nhưng trước sự kiện DOMContentLoaded.

Ví dụ sau chứng minh rằng:

<p>...content before scripts...</p>

<script>
  document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!")); // (2)
</script>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<p>...content after scripts...</p>
  1. Nội dung trang hiển thị ngay lập tức.
  2. DOMContentLoaded chờ tập lệnh hoãn lại. Nó chỉ kích hoạt khi tập lệnh (2)được tải xuống và thực thi.

Các tập lệnh hoãn lại giữ thứ tự tương đối của chúng, giống như các tập lệnh thông thường.

Vì vậy, nếu chúng ta có một tập lệnh dài trước tiên, và sau đó một tập lệnh nhỏ hơn, thì tập lệnh sau sẽ đợi.

<script defer src="https://javascript.info/article/script-async-defer/long.js"></script>
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>

Tập lệnh nhỏ tải xuống đầu tiên, chạy thứ hai

Trình duyệt quét trang để tìm tập lệnh và tải xuống song song để cải thiện hiệu suất. Vì vậy, trong ví dụ trên, cả hai tập lệnh tải xuống song song. Có lẽ small.js làm nó đầu tiên.

Nhưng đặc tả yêu cầu các tập lệnh thực thi theo thứ tự tài liệu, vì vậy nó sẽ đợi long.js thực thi.

Các thuộc tính defer duy nhất chỉ cho các kịch bản bên ngoài

Các thuộc tính defer được bỏ qua nếu thẻ <script> không có src.

2. async(không đồng bộ)

Các thuộc tính async có nghĩa là một kịch bản hoàn toàn độc lập:




  • Trang không chờ các tập lệnh không đồng bộ, nội dung được xử lý và hiển thị.
  • DOMContentLoaded và các tập lệnh không đồng bộ không đợi nhau:
    • DOMContentLoaded có thể xảy ra cả trước một tập lệnh không đồng bộ (nếu một tập lệnh không đồng bộ tải xong sau khi trang hoàn tất)
    • … Hoặc sau một tập lệnh không đồng bộ (nếu một tập lệnh không đồng bộ ngắn hoặc nằm trong bộ đệm HTTP)
  • Các tập lệnh khác không đợi tập lệnh async và các tập lệnh async không đợi chúng.

Vì vậy, nếu chúng ta có một số tập lệnh async, chúng có thể thực thi theo bất kỳ thứ tự nào. Bất cứ thứ gì tải trước – chạy trước:

<p>...content before scripts...</p>

<script>
  document.addEventListener('DOMContentLoaded', () => alert("DOM ready!"));
</script>

<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>

<p>...content after scripts...</p>
  1. Nội dung trang hiển thị ngay lập tức: async không chặn nó.
  2. DOMContentLoaded có thể xảy ra cả trước và sau async, không có gì đảm bảo ở đây.
  3. Các tập lệnh không đồng bộ không chờ đợi nhau. Một tập lệnh nhỏ hơn small.js đi thứ hai, nhưng có thể tải trước long.js, vì vậy chạy trước. Đó được gọi là thứ tự “tải trước”.

Tập lệnh không đồng bộ rất tuyệt vời khi chúng ta tích hợp tập lệnh của bên thứ ba độc lập vào trang: bộ đếm, quảng cáo, v.v., vì chúng không phụ thuộc vào tập lệnh của chúng ta và các tập lệnh của chúng ta không nên đợi chúng:

<!-- Google Analytics is usually added like this -->
<script async src="https://google-analytics.com/analytics.js"></script>

3. Tập lệnh động(Dynamic scripts)

Chúng ta cũng có thể thêm một tập lệnh động bằng JavaScript:

let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script); // (*)

Tập lệnh bắt đầu tải ngay sau khi nó được thêm vào tài liệu (*).

Các tập lệnh động hoạt động như “không đồng bộ” theo mặc định.

Đó là:

  • Họ không chờ đợi bất cứ điều gì, không có gì chờ đợi họ.
  • Tập lệnh tải trước – chạy trước (thứ tự “tải trước”).
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";

script.async = false;

document.body.append(script);

Ví dụ, ở đây chúng ta thêm hai tập lệnh. Nếu không có, script.async=false chúng sẽ thực thi theo thứ tự tải đầu tiên ( small.js có thể là đầu tiên). Nhưng với cờ đó, thứ tự là “như trong tài liệu”:

function loadScript(src) {
  let script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.body.append(script);
}

// long.js runs first because of async=false
loadScript("/article/script-async-defer/long.js");
loadScript("/article/script-async-defer/small.js");

4. Tóm lược

Cả hai async và defer có một điều chung: tải các code script không chặn việc vẽ trang. Vì vậy, người dùng có thể đọc nội dung trang và làm quen với trang ngay lập tức.

Nhưng cũng có những khác biệt cơ bản giữa chúng:


Thứ tựDOMContentLoaded
asyncThứ tự tải đầu tiên . Thứ tự tài liệu của họ không quan trọng – cái nào tải trướcKhông liên quan. Có thể tải và thực thi trong khi tài liệu chưa được tải xuống đầy đủ. Điều đó xảy ra nếu các tập lệnh có dung lượng nhỏ hoặc được lưu trong bộ nhớ cache và tài liệu đủ dài.
deferThứ tự tài liệu (khi chúng đi trong tài liệu).Thực thi sau khi tài liệu được tải và phân tích cú pháp (họ đợi nếu cần), ngay trước đó DOMContentLoaded.

Trang không có tập lệnh nên có thể sử dụng được




Xin lưu ý rằng nếu bạn đang sử dụng defer, thì trang sẽ hiển thị trước khi tập lệnh tải.

Vì vậy, người dùng có thể đọc trang, nhưng một số thành phần đồ họa có thể chưa sẵn sàng.

Cần có các chỉ báo “đang tải” ở những vị trí thích hợp và các nút bị tắt sẽ hiển thị, để người dùng có thể thấy rõ ràng những gì đã sẵn sàng và những gì chưa.

Trong thực tế, defer được sử dụng cho các tập lệnh cần toàn bộ DOM và / hoặc thứ tự thực thi tương đối của chúng là quan trọng. Và async được sử dụng cho các tập lệnh độc lập, như quầy hoặc quảng cáo. Và thứ tự thực hiện tương đối của họ không quan trọng.

Nguồn và tài liệu tiếng anh tham khảo:

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!