Vòng đời của một trang HTML có ba sự kiện quan trọng:

  • DOMContentLoaded- trình duyệt đã tải đầy đủ HTML và cây DOM được tạo, nhưng các tài nguyên bên ngoài như ảnh <img> và console có thể chưa được tải.
  • load – không chỉ HTML được tải, mà còn tất cả các tài nguyên bên ngoài: hình ảnh, kiểu, v.v.
  • beforeunload/unload – người dùng đang rời khỏi trang.

Mỗi sự kiện có thể hữu ích:

  • DOMContentLoaded event – DOM đã sẵn sàng, vì vậy trình xử lý có thể tra cứu các nút DOM, khởi tạo giao diện.
  • load sự kiện – tài nguyên bên ngoài được tải, vì vậy các kiểu được áp dụng, kích thước hình ảnh đã biết, v.v.
  • beforeunload sự kiện – người dùng rời đi: chúng tôi có thể kiểm tra xem người dùng đã lưu các thay đổi hay chưa và hỏi họ xem họ có thực sự muốn rời đi hay không.
  • unload – người dùng gần như đã rời đi, nhưng chúng ta vẫn có thể bắt đầu một số hoạt động, chẳng hạn như gửi số liệu thống kê.

Hãy cùng khám phá thông tin chi tiết về các sự kiện này.

1. DOMContentLoaded

Sự kiện DOMContentLoaded xảy ra trên đối tượng document.

Chúng ta phải sử dụng addEventListener để nắm bắt nó:

document.addEventListener(“DOMContentLoaded”, ready);

// not “document.onDOMContentLoaded = …”

Ví dụ:

<script>
  function ready() {
    alert('DOM is ready');

    // image is not yet loaded (unless it was cached), so the size is 0x0
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  }

  document.addEventListener("DOMContentLoaded", ready);
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

Trong ví dụ, trình xử lý DOMContentLoaded chạy khi tài liệu được tải, vì vậy nó có thể thấy tất cả các phần tử, bao gồm cả<img> bên dưới.

Nhưng nó không đợi hình ảnh tải. Vì vậy, alert hiển thị kích thước bằng không.

Ngay từ cái nhìn đầu tiên, sự kiện DOMContentLoaded rất đơn giản. Cây DOM đã sẵn sàng – đây là sự kiện. Tuy nhiên, có một số điểm đặc biệt.

1.1. DOMContentLoaded và các tập lệnh

Khi trình duyệt xử lý một tài liệu HTML và bắt gặp một thẻ <script>, nó cần thực thi trước khi tiếp tục xây dựng DOM. Đó là một biện pháp phòng ngừa, vì các tập lệnh có thể muốn sửa đổi DOM và thậm chí vào nó document.write, vì vậy DOMContentLoaded phải chờ đợi.

Vì vậy, DOMContentLoaded chắc chắn xảy ra sau các tập lệnh như vậy:

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

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>

<script>
  alert("Library loaded, inline script executed");
</script>

Trong ví dụ trên, trước tiên chúng ta thấy “Đã tải thư viện…”, sau đó là “DOM đã sẵn sàng!” (tất cả các tập lệnh được thực thi).

Các tập lệnh không chặn DOMContentLoaded

Có hai ngoại lệ từ quy tắc này:

  1. Tập lệnh có thuộc tính async mà chúng ta sẽ đề cập sau một chút , không chặn DOMContentLoaded.
  2. Các tập lệnh được tạo động với document.createElement(‘script’) và sau đó được thêm vào trang web cũng không chặn sự kiện này.

1.2. DOMContentLoaded và kiểu(style)

Các style sheets bên ngoài không ảnh hưởng đến DOM, vì vậy DOMContentLoaded đừng đợi chúng.

Nhưng có một cạm bẫy. Nếu chúng ta có một tập lệnh sau kiểu(style), thì tập lệnh đó phải đợi cho đến khi style sheets tải:

<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // the script doesn't not execute until the stylesheet is loaded
  alert(getComputedStyle(document.body).marginTop);
</script>

Lý do cho điều này là script có thể muốn lấy tọa độ và các thuộc tính phụ thuộc kiểu khác của các phần tử, như trong ví dụ trên. Đương nhiên, nó phải đợi các kiểu để tải.

Khi DOMContentLoaded chờ các tập lệnh, giờ đây nó cũng chờ các kiểu trước chúng.

1.3. Tự động điền trình duyệt tích hợp

Bật biểu mẫu tự động điền của Firefox, Chrome và Opera DOMContentLoaded.

Ví dụ: nếu trang có biểu mẫu có thông tin đăng nhập và mật khẩu và trình duyệt ghi nhớ các giá trị, thì trên DOMContentLoaded có thể cố gắng tự động điền chúng (nếu được người dùng chấp thuận).

Vì vậy, nếu DOMContentLoaded bị trì hoãn bởi các tập lệnh tải lâu, thì tính năng tự động điền cũng đang chờ. Bạn có thể đã thấy điều đó trên một số trang web (nếu bạn sử dụng tính năng tự động điền của trình duyệt) – các trường đăng nhập / mật khẩu không được tự động điền ngay lập tức, nhưng sẽ có độ trễ cho đến khi trang tải hoàn toàn. Đó thực sự là độ trễ cho đến khi sự kiện DOMContentLoaded diễn ra.

2. window.onload

Sự kiện load trên đối tượng window sẽ kích hoạt khi toàn bộ trang được tải bao gồm kiểu, hình ảnh và các tài nguyên khác. Sự kiện này có sẵn thông qua thuộc tính onload.

Ví dụ bên dưới hiển thị chính xác kích thước hình ảnh, vì window.onload chờ tất cả hình ảnh:

<!--
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
-->

<script>
  window.onload = function() { // same as window.addEventListener('load', (event) => {
    alert('Page loaded');

    // image is loaded at this time
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  };
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

3. window.onunload

Khi khách truy cập rời khỏi trang, sự kiện unload sẽ kích hoạt window. Chúng ta có thể làm điều gì đó mà không liên quan đến sự chậm trễ, chẳng hạn như đóng các cửa sổ bật lên có liên quan.

Ngoại lệ đáng chú ý là gửi phân tích.

Giả sử chúng ta thu thập dữ liệu về cách trang được sử dụng: nhấp chuột, cuộn, vùng trang đã xem, v.v.

Đương nhiên, sự kiện unload là khi người dùng rời khỏi chúng ta và chúng ta muốn lưu dữ liệu trên máy chủ của mình.

Có một phương pháp navigator.sendBeacon(url, data) đặc biệt cho những nhu cầu như vậy, được mô tả trong đặc tả https://w3c.github.io/beacon/ .

Nó gửi dữ liệu trong nền. Quá trình chuyển đổi sang trang khác không bị trì hoãn: trình duyệt rời khỏi trang, nhưng vẫn thực hiện sendBeacon.

Đây là cách sử dụng nó:

let analyticsData = { /* object with gathered data */ };

window.addEventListener("unload", function() {
  navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
};
  • Yêu cầu được gửi dưới dạng POST.
  • Chúng ta có thể gửi không chỉ một chuỗi mà còn các biểu mẫu và các định dạng khác, như được mô tả trong chương Tìm nạp , nhưng thường thì đó là một đối tượng được đổi thành chuỗi .
  • Dữ liệu được giới hạn bởi 64kb.

Khi yêu cầu sendBeacon kết thúc, trình duyệt có thể đã rời khỏi tài liệu, vì vậy không có cách nào để nhận được phản hồi của máy chủ (thường trống để phân tích).

Cũng có một cờ keepalive để thực hiện các yêu cầu “sau trang bên trái” như vậy trong phương thức tìm nạp cho các yêu cầu mạng chung. Bạn có thể tìm thêm thông tin trong chương Tìm nạp API .

Nếu chúng ta muốn hủy chuyển đổi sang trang khác, chúng ta không thể thực hiện ở đây. Nhưng chúng ta có thể sử dụng một sự kiện khác – onbeforeunload.

4. window.onbeforeunload

Nếu khách truy cập bắt đầu điều hướng khỏi trang hoặc cố gắng đóng cửa sổ, trình xử lý beforeunload sẽ yêu cầu xác nhận bổ sung.

Nếu chúng ta hủy sự kiện, trình duyệt có thể hỏi khách truy cập xem họ có chắc chắn không.

Bạn có thể thử nó bằng cách chạy code này và sau đó tải lại trang:

window.onbeforeunload = function() {
  return false;
};

Vì lý do lịch sử, trả về một chuỗi không trống cũng được tính là hủy sự kiện. Một thời gian trước, các trình duyệt thường hiển thị nó dưới dạng một thông báo, nhưng như thông số kỹ thuật hiện đại cho biết, chúng không nên.

Đây là một ví dụ:

window.onbeforeunload = function() {
  return "There are unsaved changes. Leave now?";
};

Hành vi đã bị thay đổi do một số quản trị viên web đã lạm dụng trình xử lý sự kiện này bằng cách hiển thị các thông báo gây hiểu lầm và gây phiền nhiễu. Vì vậy, ngay bây giờ các trình duyệt cũ vẫn có thể hiển thị nó dưới dạng thông báo, nhưng ngoài điều đó – không có cách nào để tùy chỉnh thông báo hiển thị cho người dùng.

5. readyState

Điều gì xảy ra nếu chúng ta đặt trình xử lý DOMContentLoaded sau khi tài liệu được tải?

Đương nhiên, nó không bao giờ chạy.

Có những trường hợp chúng ta không chắc liệu tài liệu đã sẵn sàng hay chưa. Chúng ta muốn hàm của chúng ta thực thi khi DOM được tải, dù là bây giờ hoặc sau này.

Các thuộc tính document.readyState  cho chúng ta biết về tình trạng tải hiện hành.

Có 3 giá trị có thể có:

  • “loading” – tài liệu đang tải.
  • “interactive” – tài liệu đã được đọc đầy đủ.
  • “complete” – tài liệu đã được đọc đầy đủ và tất cả tài nguyên (như hình ảnh) cũng được tải.

Vì vậy, chúng ta có thể kiểm tra document.readyState và thiết lập một trình xử lý hoặc thực thi code ngay lập tức nếu nó đã sẵn sàng.

Như thế này:

function work() { /*...*/ }

if (document.readyState == 'loading') {
  // still loading, wait for the event
  document.addEventListener('DOMContentLoaded', work);
} else {
  // DOM is ready!
  work();
}

Ngoài ra còn có sự kiện readystatechange kích hoạt khi trạng thái thay đổi, vì vậy chúng ta có thể in tất cả các trạng thái này như sau:

// current state
console.log(document.readyState);

// print state changes
document.addEventListener('readystatechange', () => console.log(document.readyState));

Sự kiện readystatechange này là một cơ chế thay thế theo dõi trạng thái tải tài liệu, nó đã xuất hiện từ lâu. Ngày nay, nó ít được sử dụng.

Hãy xem toàn bộ dòng sự kiện để biết sự đầy đủ.

Dưới đây là một tài liệu với <iframe>, <img> và xử lý các sự kiện đăng nhập:

<script>
  log('initial readyState:' + document.readyState);

  document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
  document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));

  window.onload = () => log('window onload');
</script>

<iframe src="iframe.html" onload="log('iframe onload')"></iframe>

<img src="http://en.js.cx/clipart/train.gif" id="img">
<script>
  img.onload = () => log('img onload');
</script>

Ví dụ làm việc là trong hộp cát .

Đầu ra điển hình:

  1. [1] ban đầu readyState: đang tải
  2. [2] readyState: tương tác
  3. [2] DOMContentLoaded
  4. [3] khi tải iframe
  5. [4] img onload
  6. [4] readyState: hoàn thành
  7. [4] window onload

Các số trong ngoặc vuông biểu thị thời gian gần đúng khi nó xảy ra. Các sự kiện có nhãn cùng một chữ số xảy ra gần như cùng một lúc (± vài mili giây).

  • document.readyState trở nên tương tác đúng trước đây là DOMContentLoaded. Hai điều này thực sự có nghĩa giống nhau.
  • document.readyState hoàn thành khi tất cả tài nguyên ( iframe và img) được tải. Ở đây chúng ta có thể thấy rằng nó xảy ra trong khoảng thời gian giống như img.onload( img là tài nguyên cuối cùng) và window.onload. Chuyển sang trạng thái complete có nghĩa là giống như window.onload. Sự khác biệt là nó window.onload luôn hoạt động sau tất cả các trình xử lý load khác.

6. Tóm lược

Sự kiện tải trang:

  • Sự kiện DOMContentLoaded sẽ kích hoạt document khi DOM sẵn sàng. Chúng ta có thể áp dụng JavaScript cho các phần tử ở giai đoạn này.
    • Tập lệnh chẳng hạn như <script>...</script> or <script src="..."></script> chặn DOMContentLoaded, trình duyệt đợi chúng thực thi.
    • Hình ảnh và các tài nguyên khác vẫn có thể tiếp tục tải.
  • Sự kiện load trên window sẽ kích hoạt khi trang và tất cả tài nguyên được tải. Chúng tôi hiếm khi sử dụng nó, vì thường không cần đợi quá lâu.
  • Sự kiện beforeunload trên window kích hoạt khi người dùng muốn rời khỏi trang. Nếu chúng ta hủy sự kiện, trình duyệt sẽ hỏi liệu người dùng có thực sự muốn rời đi hay không (ví dụ: chúng ta có các thay đổi chưa được lưu).
  • Sự kiện unload window kích hoạt khi người dùng cuối cùng rời đi, trong trình xử lý, chúng ta chỉ có thể làm những việc đơn giản không liên quan đến sự chậm trễ hoặc yêu cầu người dùng. Vì hạn chế đó, nó hiếm khi được sử dụng. Chúng ta có thể gửi yêu cầu mạng với navigator.sendBeacon.
  • document.readyState là trạng thái hiện tại của tài liệu, các thay đổi có thể được theo dõi trong readystatechange trường hợp:
    • loading – tài liệu đang tải.
    • interactive- tài liệu được phân tích cú pháp, xảy ra cùng lúc với DOMContentLoaded nhưng trước nó.
    • complete – tài liệu và tài nguyên được tải, xảy ra cùng lúc với window.onload nhưng trước nó.

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!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!