HTML 頁面的生命週期有三個重要的事件
DOMContentLoaded
– 瀏覽器已完全載入 HTML,並建構好 DOM 樹,但外部資源(例如圖片<img>
和樣式表)可能尚未載入。load
– 不僅載入 HTML,也載入所有外部資源:圖片、樣式等。beforeunload/unload
– 使用者離開頁面。
每個事件都可能很有用
DOMContentLoaded
事件 – DOM 已準備好,因此處理常式可以查詢 DOM 節點,初始化介面。load
事件 – 外部資源已載入,因此樣式已套用、已知影像大小等。beforeunload
事件 – 使用者即將離開:我們可以檢查使用者是否已儲存變更,並詢問他們是否真的要離開。unload
– 使用者幾乎已離開,但我們仍然可以啟動一些作業,例如傳送統計資料。
讓我們探討這些事件的詳細資料。
DOMContentLoaded
DOMContentLoaded
事件發生在 document
物件上。
我們必須使用 addEventListener
來捕捉它
document.addEventListener("DOMContentLoaded", ready);
// not "document.onDOMContentLoaded = ..."
例如
<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">
在範例中,DOMContentLoaded
處理常式在文件載入時執行,因此它可以看到所有元素,包括下方的 <img>
。
但它不會等待影像載入。因此 alert
會顯示零大小。
乍看之下,DOMContentLoaded
事件非常簡單。DOM 樹已準備好 – 以下是事件。不過有一些特殊之處。
DOMContentLoaded 和腳本
當瀏覽器處理 HTML 文件並遇到 <script>
標籤時,它需要在繼續建置 DOM 之前執行。這是一個預防措施,因為腳本可能想要修改 DOM,甚至 document.write
到其中,因此 DOMContentLoaded
必須等待。
因此 DOMContentLoaded 肯定會在這些腳本之後發生
<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>
在上面的範例中,我們首先看到「程式庫已載入…」,然後看到「DOM 已準備好!」(所有腳本都已執行)。
此規則有兩個例外
- 具有
async
屬性的腳本,我們將在 稍後介紹,不會封鎖DOMContentLoaded
。 - 使用
document.createElement('script')
動態產生的腳本,然後新增到網頁中,也不會封鎖此事件。
DOMContentLoaded 和樣式
外部樣式表不會影響 DOM,因此 DOMContentLoaded
不會等待它們。
但有一個陷阱。如果我們在樣式之後有一個腳本,那麼該腳本必須等到樣式表載入
<link type="text/css" rel="stylesheet" href="style.css">
<script>
// the script doesn't execute until the stylesheet is loaded
alert(getComputedStyle(document.body).marginTop);
</script>
這是因為腳本可能想要取得元素的座標和其他依樣式而定的屬性,就像上面的範例一樣。自然而然地,它必須等到樣式載入。
由於 DOMContentLoaded
會等待腳本,現在它也會在腳本之前等待樣式。
內建瀏覽器自動填寫
Firefox、Chrome 和 Opera 會在 DOMContentLoaded
上自動填寫表單。
例如,如果頁面有一個包含登入和密碼的表單,而且瀏覽器記住了這些值,那麼在 DOMContentLoaded
上,它可能會嘗試自動填寫這些值(如果使用者同意)。
因此,如果 DOMContentLoaded
被載入時間長的腳本延後,那麼自動填寫也會等待。你可能在某些網站上看過(如果你使用瀏覽器自動填寫)——登入/密碼欄位並不會立即自動填寫,而是會延遲到頁面完全載入。那實際上就是延遲到 DOMContentLoaded
事件。
window.onload
window
物件上的 load
事件會在整個頁面載入,包括樣式、圖片和其他資源時觸發。此事件可透過 onload
屬性取得。
下面的範例正確顯示圖片大小,因為 window.onload
會等待所有圖片
<script>
window.onload = function() { // can also use 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">
window.onunload
當訪客離開頁面時,unload
事件會在 window
上觸發。我們可以在那裡做一些不涉及延遲的事情,例如關閉相關的彈出式視窗。
值得注意的例外是傳送分析資料。
假設我們收集有關頁面如何使用的資料:滑鼠點擊、捲動、檢視的頁面區域等等。
自然而然地,unload
事件是使用者離開我們時,我們會希望將資料儲存在我們的伺服器上。
規範 https://w3c.github.io/beacon/ 中描述了一種特殊的 navigator.sendBeacon(url, data)
方法,可用於此類需求。
它會在背景中傳送資料。不會延遲轉移到另一個頁面:瀏覽器會離開頁面,但仍會執行 sendBeacon
。
以下是使用方法
let analyticsData = { /* object with gathered data */ };
window.addEventListener("unload", function() {
navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
});
- 請求會以 POST 方式傳送。
- 我們不僅可以傳送字串,還可以傳送表單和其他格式,如 Fetch 章節中所述,但通常它是一個字串化的物件。
- 資料限制為 64kb。
當 sendBeacon
請求完成時,瀏覽器可能已經離開文件,因此無法取得伺服器回應(對於分析資料來說通常是空的)。
對於一般網路請求,fetch 方法中也有 keepalive
旗標,可用於執行此類「離開頁面後」請求。你可以在 Fetch API 章節中找到更多資訊。
如果我們想要取消轉移到另一個頁面,我們無法在此處執行此操作。但我們可以使用另一個事件——onbeforeunload
。
window.onbeforeunload
如果訪客離開頁面或嘗試關閉視窗,beforeunload
處理常式會要求額外的確認。
如果我們取消事件,瀏覽器可能會詢問訪客是否確定。
你可以執行這段程式碼並重新整理頁面來嘗試看看
window.onbeforeunload = function() {
return false;
};
出於歷史原因,傳回非空字串也會視為取消事件。一段時間以前,瀏覽器會將其顯示為訊息,但正如 現代規格 所述,瀏覽器不應該這麼做。
以下是一個範例
window.onbeforeunload = function() {
return "There are unsaved changes. Leave now?";
};
行為已變更,因為某些網站管理員會濫用此事件處理常式,顯示誤導性和令人討厭的訊息。因此,現在舊版瀏覽器仍然可能會將其顯示為訊息,但除此之外,沒有辦法自訂顯示給使用者的訊息。
event.preventDefault()
不會從 beforeunload
處理常式運作這聽起來可能很奇怪,但大多數瀏覽器會忽略 event.preventDefault()
。
這表示下列程式碼可能無法運作
window.addEventListener("beforeunload", (event) => {
// doesn't work, so this event handler doesn't do anything
event.preventDefault();
});
相反地,在這些處理常式中,應該將 event.returnValue
設定為字串,以取得與上述程式碼類似的結果
window.addEventListener("beforeunload", (event) => {
// works, same as returning from window.onbeforeunload
event.returnValue = "There are unsaved changes. Leave now?";
});
readyState
如果我們在載入文件後設定 DOMContentLoaded
處理常式,會發生什麼事?
當然,它永遠不會執行。
有時候我們不確定文件是否已準備好。我們希望我們的函式在 DOM 載入時執行,無論是在現在或稍後。
document.readyState
屬性會告訴我們目前的載入狀態。
有 3 個可能的值
"loading"
– 文件正在載入。"interactive"
– 文件已完全讀取。"complete"
– 文件已完全讀取,所有資源(例如圖片)也已載入。
因此,我們可以檢查 document.readyState
,並在準備好時設定處理常式或執行程式碼。
像這樣
function work() { /*...*/ }
if (document.readyState == 'loading') {
// still loading, wait for the event
document.addEventListener('DOMContentLoaded', work);
} else {
// DOM is ready!
work();
}
還有 readystatechange
事件,當狀態變更時會觸發,因此我們可以像這樣列印所有這些狀態
// current state
console.log(document.readyState);
// print state changes
document.addEventListener('readystatechange', () => console.log(document.readyState));
readystatechange
事件是追蹤文件載入狀態的另一種機制,它很早以前就出現了。現在很少使用它了。
讓我們看看完整的事件流程,以求完整性。
這裡有一個包含 <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="https://en.js.cx/clipart/train.gif" id="img">
<script>
img.onload = () => log('img onload');
</script>
實際範例在 沙盒中。
典型輸出
- [1] 初始 readyState:loading
- [2] readyState:interactive
- [2] DOMContentLoaded
- [3] iframe onload
- [4] img onload
- [4] readyState:complete
- [4] window onload
方括號中的數字表示發生時間的近似值。標記有相同數字的事件近似在同一時間發生 (± 幾毫秒)。
document.readyState
在DOMContentLoaded
之前立即變為interactive
。這兩件事實際上是同一個意思。- 當所有資源(
iframe
和img
)載入時,document.readyState
會變為complete
。這裡我們可以看到它發生在與img.onload
(img
是最後一個資源)和window.onload
大致相同的時間。切換到complete
狀態與window.onload
的意思相同。不同的是,window.onload
總是在所有其他load
處理程序之後才執行。
摘要
頁面載入事件
- 當 DOM 準備就緒時,
DOMContentLoaded
事件會在document
上觸發。我們可以在這個階段將 JavaScript 套用至元素。- 例如
<script>...</script>
或<script src="..."></script>
的腳本會阻擋 DOMContentLoaded,瀏覽器會等待它們執行。 - 影像和其他資源也可能繼續載入。
- 例如
- 當頁面和所有資源載入時,
window
上的load
事件會觸發。我們很少使用它,因為通常不需要等待這麼久。 - 當使用者想要離開頁面時,
window
上的beforeunload
事件會觸發。如果我們取消事件,瀏覽器會詢問使用者是否真的想要離開(例如我們有未儲存的變更)。 - 當使用者最終離開時,
window
上的unload
事件會觸發,在處理程序中我們只能執行不涉及延遲或詢問使用者的簡單操作。由於這個限制,它很少被使用。我們可以使用navigator.sendBeacon
發送網路請求。 document.readyState
是文件的當前狀態,可以在readystatechange
事件中追蹤變更loading
– 文件正在載入。interactive
– 文件已剖析,發生在與DOMContentLoaded
大致相同但早於它的時間。complete
– 文件和資源已載入,發生在與window.onload
大約相同時間,但發生在它之前。
留言
<code>
標籤,對於多行,請將它們包覆在<pre>
標籤中,對於超過 10 行,請使用沙盒 (plnkr、jsbin、codepen…)