W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
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
事件發(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ā)條件。它并沒有什么特別之處。
當瀏覽器處理一個 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ī)則有兩個例外:
- 具有 ?
async
? 特性(attribute)的腳本不會阻塞 ?DOMContentLoaded
?,稍后 我們會講到。- 使用 ?
document.createElement('script')
? 動態(tài)生成并添加到網(wǎng)頁的腳本也不會阻塞 ?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)在也在等待腳本前面的樣式。
Firefox,Chrome 和 Opera 都會在 DOMContentLoaded
中自動填充表單。
例如,如果頁面有一個帶有登錄名和密碼的表單,并且瀏覽器記住了這些值,那么在 DOMContentLoaded
上,瀏覽器會嘗試自動填充它們(如果得到了用戶允許)。
因此,如果 DOMContentLoaded
被需要加載很長時間的腳本延遲觸發(fā),那么自動填充也會等待。你可能在某些網(wǎng)站上看到過(如果你使用瀏覽器自動填充)—— 登錄名/密碼字段不會立即自動填充,而是在頁面被完全加載前會延遲填充。這實際上是 DOMContentLoaded
事件之前的延遲。
當整個頁面,包括樣式、圖片和其他資源被加載完成時,會觸發(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
對象上的 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));
});
當 sendBeacon
請求完成時,瀏覽器可能已經(jīng)離開了文檔,所以就無法獲取服務(wù)器響應(yīng)(對于分析數(shù)據(jù)來說通常為空)。
還有一個 keep-alive
標志,該標志用于在 fetch 方法中為通用的網(wǎng)絡(luò)請求執(zhí)行此類“離開頁面后”的請求。你可以在 Fetch API 一章中找到更多相關(guān)信息。
如果我們要取消跳轉(zhuǎn)到另一頁面的操作,在這里做不到。但是我們可以使用另一個事件 —— 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 = "有未保存的值。確認要離開嗎?"; });
如果我們在文檔加載完成之后設(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 中。
典型輸出:
方括號中的數(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
? 處理程序之后運行。頁面生命周期事件:
document
? 上的 ?DOMContentLoaded
? 事件就會被觸發(fā)。在這個階段,我們可以將 JavaScript 應(yīng)用于元素。<script>...</script>
? 或 ?<script src="..."></script>
? 之類的腳本會阻塞 ?DOMContentLoaded
?,瀏覽器將等待它們執(zhí)行結(jié)束。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ā)生。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: