Javascript 頁面生命周期:DOMContentLoaded,load,beforeunload,unload

2023-02-17 10:55 更新

HTML 頁面的生命周期包含三個重要事件:

  • ?DOMContentLoaded? —— 瀏覽器已完全加載 HTML,并構(gòu)建了 DOM 樹,但像 ?<img>? 和樣式表之類的外部資源可能尚未加載完成。
  • ?load? —— 瀏覽器不僅加載完成了 HTML,還加載完成了所有外部資源:圖片,樣式等。
  • ?beforeunload/unload? —— 當用戶正在離開頁面時。

每個事件都是有用的:

  • ?DOMContentLoaded? 事件 —— DOM 已經(jīng)就緒,因此處理程序可以查找 DOM 節(jié)點,并初始化接口。
  • ?load? 事件 —— 外部資源已加載完成,樣式已被應(yīng)用,圖片大小也已知了。
  • ?beforeunload? 事件 —— 用戶正在離開:我們可以檢查用戶是否保存了更改,并詢問他是否真的要離開。
  • ?unload? 事件 —— 用戶幾乎已經(jīng)離開了,但是我們?nèi)匀豢梢詥右恍┎僮?,例如發(fā)送統(tǒng)計數(shù)據(jù)。

我們探索一下這些事件的細節(jié)。

DOMContentLoaded

DOMContentLoaded 事件發(fā)生在 document 對象上。

我們必須使用 addEventListener 來捕獲它:

document.addEventListener("DOMContentLoaded", ready);
// 不是 "document.onDOMContentLoaded = ..."

例如:

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

    // 圖片目前尚未加載完成(除非已經(jīng)被緩存),所以圖片的大小為 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" rel="external nofollow"  rel="external nofollow" >

在示例中,DOMContentLoaded 處理程序在文檔加載完成后觸發(fā),所以它可以查看所有元素,包括它下面的 <img> 元素。

但是,它不會等待圖片加載。因此,alert 顯示其大小為零。

乍一看,DOMContentLoaded 事件非常簡單。DOM 樹準備就緒 —— 這是它的觸發(fā)條件。它并沒有什么特別之處。

DOMContentLoaded 和腳本

當瀏覽器處理一個 HTML 文檔,并在文檔中遇到 <script> 標簽時,就會在繼續(xù)構(gòu)建 DOM 之前運行它。這是一種防范措施,因為腳本可能想要修改 DOM,甚至對其執(zhí)行 document.write 操作,所以 DOMContentLoaded 必須等待腳本執(zhí)行結(jié)束。

因此,DOMContentLoaded 肯定在下面的這些腳本執(zhí)行結(jié)束之后發(fā)生:

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

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

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

在上面這個例子中,我們首先會看到 “Library loaded…”,然后才會看到 “DOM ready!”(所有腳本都已經(jīng)執(zhí)行結(jié)束)。

不會阻塞 ?DOMContentLoaded? 的腳本

此規(guī)則有兩個例外:

  1. 具有 ?async? 特性(attribute)的腳本不會阻塞 ?DOMContentLoaded?,稍后 我們會講到。
  2. 使用 ?document.createElement('script')? 動態(tài)生成并添加到網(wǎng)頁的腳本也不會阻塞 ?DOMContentLoaded?。

DOMContentLoaded 和樣式

外部樣式表不會影響 DOM,因此 DOMContentLoaded 不會等待它們。

但這里有一個陷阱。如果在樣式后面有一個腳本,那么該腳本必須等待樣式表加載完成:

<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // 在樣式表加載完成之前,腳本都不會執(zhí)行
  alert(getComputedStyle(document.body).marginTop);
</script>

原因是,腳本可能想要獲取元素的坐標和其他與樣式相關(guān)的屬性,如上例所示。因此,它必須等待樣式加載完成。

當 DOMContentLoaded 等待腳本時,它現(xiàn)在也在等待腳本前面的樣式。

瀏覽器內(nèi)建的自動填充

Firefox,Chrome 和 Opera 都會在 DOMContentLoaded 中自動填充表單。

例如,如果頁面有一個帶有登錄名和密碼的表單,并且瀏覽器記住了這些值,那么在 DOMContentLoaded 上,瀏覽器會嘗試自動填充它們(如果得到了用戶允許)。

因此,如果 DOMContentLoaded 被需要加載很長時間的腳本延遲觸發(fā),那么自動填充也會等待。你可能在某些網(wǎng)站上看到過(如果你使用瀏覽器自動填充)—— 登錄名/密碼字段不會立即自動填充,而是在頁面被完全加載前會延遲填充。這實際上是 DOMContentLoaded 事件之前的延遲。

window.onload

當整個頁面,包括樣式、圖片和其他資源被加載完成時,會觸發(fā) window 對象上的 load 事件。可以通過 onload 屬性獲取此事件。

下面的這個示例正確顯示了圖片大小,因為 window.onload 會等待所有圖片加載完畢:

<script>
  window.onload = function() { // 也可以用 window.addEventListener('load', (event) => {
    alert('Page loaded');

    // 此時圖片已經(jīng)加載完成
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  };
</script>

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

window.onunload

當訪問者離開頁面時,window 對象上的 unload 事件就會被觸發(fā)。我們可以在那里做一些不涉及延遲的操作,例如關(guān)閉相關(guān)的彈出窗口。

有一個值得注意的特殊情況是發(fā)送分析數(shù)據(jù)。

假設(shè)我們收集有關(guān)頁面使用情況的數(shù)據(jù):鼠標點擊,滾動,被查看的頁面區(qū)域等。

自然地,當用戶要離開的時候,我們希望通過 unload 事件將數(shù)據(jù)保存到我們的服務(wù)器上。

有一個特殊的 navigator.sendBeacon(url, data) 方法可以滿足這種需求,詳見規(guī)范 https://w3c.github.io/beacon/。

它在后臺發(fā)送數(shù)據(jù),轉(zhuǎn)換到另外一個頁面不會有延遲:瀏覽器離開頁面,但仍然在執(zhí)行 sendBeacon。

使用方式如下:

let analyticsData = { /* 帶有收集的數(shù)據(jù)的對象 */ };

window.addEventListener("unload", function() {
  navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
});
  • 請求以 POST 方式發(fā)送。
  • 我們不僅能發(fā)送字符串,還能發(fā)送表單以及其他格式的數(shù)據(jù),在 Fetch 一章有詳細講解,但通常它是一個字符串化的對象。
  • 數(shù)據(jù)大小限制在 64kb。

當 sendBeacon 請求完成時,瀏覽器可能已經(jīng)離開了文檔,所以就無法獲取服務(wù)器響應(yīng)(對于分析數(shù)據(jù)來說通常為空)。

還有一個 keep-alive 標志,該標志用于在 fetch 方法中為通用的網(wǎng)絡(luò)請求執(zhí)行此類“離開頁面后”的請求。你可以在 Fetch API 一章中找到更多相關(guān)信息。

如果我們要取消跳轉(zhuǎn)到另一頁面的操作,在這里做不到。但是我們可以使用另一個事件 —— onbeforeunload。

window.onbeforeunload

如果訪問者觸發(fā)了離開頁面的導航(navigation)或試圖關(guān)閉窗口,beforeunload 處理程序?qū)⒁筮M行更多確認。

如果我們要取消事件,瀏覽器會詢問用戶是否確定。

你可以通過運行下面這段代碼,然后重新加載頁面來進行嘗試:

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

由于歷史原因,返回非空字符串也被視為取消事件。在以前,瀏覽器曾經(jīng)將其顯示為消息,但是根據(jù) 現(xiàn)代規(guī)范 所述,它們不應(yīng)該這樣。

這里有個例子:

window.onbeforeunload = function() {
  return "有未保存的值。確認要離開嗎?";
};

它的行為已經(jīng)改變了,因為有些站長通過顯示誤導性和惡意信息濫用了此事件處理程序。所以,目前一些舊的瀏覽器可能仍將其顯示為消息,但除此之外 —— 無法自定義顯示給用戶的消息。

?event.preventDefault()? 在 ?beforeunload? 處理程序中不起作用

這聽起來可能很奇怪,但大多數(shù)瀏覽器都會忽略 event.preventDefault()。

這意味著,以下代碼可能不起作用:

window.addEventListener("beforeunload", (event) => {
  // 不起作用,所以這個事件處理程序沒做任何事兒
  event.preventDefault();
});

相反,在這樣的處理程序中,應(yīng)該將 event.returnValue 設(shè)置為一個字符串,以獲得類似于上面代碼的結(jié)果:

window.addEventListener("beforeunload", (event) => {
  // 起作用,與在 window.onbeforeunload 中 return 值的效果是一樣的
  event.returnValue = "有未保存的值。確認要離開嗎?";
});

readyState

如果我們在文檔加載完成之后設(shè)置 DOMContentLoaded 事件處理程序,會發(fā)生什么?

很自然地,它永遠不會運行。

在某些情況下,我們不確定文檔是否已經(jīng)準備就緒。我們希望我們的函數(shù)在 DOM 加載完成時執(zhí)行,無論現(xiàn)在還是以后。

document.readyState 屬性可以為我們提供當前加載狀態(tài)的信息。

它有 3 個可能值:

  • ?loading? —— 文檔正在被加載。
  • ?interactive? —— 文檔被全部讀取。
  • ?complete? —— 文檔被全部讀取,并且所有資源(例如圖片等)都已加載完成。

所以,我們可以檢查 document.readyState 并設(shè)置一個處理程序,或在代碼準備就緒時立即執(zhí)行它。

像這樣:

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

if (document.readyState == 'loading') {
  // 仍在加載,等待事件
  document.addEventListener('DOMContentLoaded', work);
} else {
  // DOM 已就緒!
  work();
}

還有一個 readystatechange 事件,會在狀態(tài)發(fā)生改變時觸發(fā),因此我們可以打印所有這些狀態(tài),就像這樣:

// 當前狀態(tài)
console.log(document.readyState);

// 狀態(tài)改變時打印它
document.addEventListener('readystatechange', () => console.log(document.readyState));

readystatechange 事件是跟蹤文檔加載狀態(tài)的另一種機制,它很早就存在了?,F(xiàn)在則很少被使用。

但是為了完整起見,讓我們看看完整的事件流。

這是一個帶有 <iframe><img> 和記錄事件的處理程序的文檔:

<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" rel="external nofollow"  id="img">
<script>
  img.onload = () => log('img onload');
</script>

此示例運行 在 sandbox 中。

典型輸出:

  1. [1] initial readyState:loading
  2. [2] readyState:interactive
  3. [2] DOMContentLoaded
  4. [3] iframe onload
  5. [4] img onload
  6. [4] readyState:complete
  7. [4] window onload

方括號中的數(shù)字表示發(fā)生這種情況的大致時間。標有相同數(shù)字的事件幾乎是同時發(fā)生的(± 幾毫秒)。

  • 在 ?DOMContentLoaded? 之前,?document.readyState? 會立即變成 ?interactive?。它們倆的意義實際上是相同的。
  • 當所有資源(?iframe? 和 ?img?)都加載完成后,?document.readyState? 變成 ?complete?。這里我們可以發(fā)現(xiàn),它與 ?img.onload?(?img? 是最后一個資源)和 ?window.onload? 幾乎同時發(fā)生。轉(zhuǎn)換到 ?complete? 狀態(tài)的意義與 ?window.onload? 相同。區(qū)別在于 ?window.onload? 始終在所有其他 ?load? 處理程序之后運行。

總結(jié)

頁面生命周期事件:

  • 當 DOM 準備就緒時,?document? 上的 ?DOMContentLoaded? 事件就會被觸發(fā)。在這個階段,我們可以將 JavaScript 應(yīng)用于元素。
    • 諸如 ?<script>...</script>? 或 ?<script src="..."></script>? 之類的腳本會阻塞 ?DOMContentLoaded?,瀏覽器將等待它們執(zhí)行結(jié)束。
    • 圖片和其他資源仍然可以繼續(xù)被加載。
  • 當頁面和所有資源都加載完成時,?window? 上的 ?load? 事件就會被觸發(fā)。我們很少使用它,因為通常無需等待那么長時間。
  • 當用戶想要離開頁面時,?window? 上的 ?beforeunload? 事件就會被觸發(fā)。如果我們?nèi)∠@個事件,瀏覽器就會詢問我們是否真的要離開(例如,我們有未保存的更改)。
  • 當用戶最終離開時,?window? 上的 ?unload? 事件就會被觸發(fā)。在處理程序中,我們只能執(zhí)行不涉及延遲或詢問用戶的簡單操作。正是由于這個限制,它很少被使用。我們可以使用 ?navigator.sendBeacon? 來發(fā)送網(wǎng)絡(luò)請求。
  • ?document.readyState? 是文檔的當前狀態(tài),可以在 ?readystatechange? 事件中跟蹤狀態(tài)更改:
    • ?loading? —— 文檔正在被加載。
    • ?interactive? —— 文檔已被解析完成,與 ?DOMContentLoaded? 幾乎同時發(fā)生,但是在 ?DOMContentLoaded? 之前發(fā)生。
    • ?complete? —— 文檔和資源均已加載完成,與 ?window.onload? 幾乎同時發(fā)生,但是在 ?window.onload? 之前發(fā)生。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號