W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
HTML 頁(yè)面的生命周期包含三個(gè)重要事件:
DOMContentLoaded
? —— 瀏覽器已完全加載 HTML,并構(gòu)建了 DOM 樹(shù),但像 ?<img>
? 和樣式表之類(lèi)的外部資源可能尚未加載完成。load
? —— 瀏覽器不僅加載完成了 HTML,還加載完成了所有外部資源:圖片,樣式等。beforeunload/unload
? —— 當(dāng)用戶(hù)正在離開(kāi)頁(yè)面時(shí)。每個(gè)事件都是有用的:
DOMContentLoaded
? 事件 —— DOM 已經(jīng)就緒,因此處理程序可以查找 DOM 節(jié)點(diǎn),并初始化接口。load
? 事件 —— 外部資源已加載完成,樣式已被應(yīng)用,圖片大小也已知了。beforeunload
? 事件 —— 用戶(hù)正在離開(kāi):我們可以檢查用戶(hù)是否保存了更改,并詢(xún)問(wèn)他是否真的要離開(kāi)。unload
? 事件 —— 用戶(hù)幾乎已經(jīng)離開(kāi)了,但是我們?nèi)匀豢梢詥?dòng)一些操作,例如發(fā)送統(tǒng)計(jì)數(shù)據(jù)。我們探索一下這些事件的細(xì)節(jié)。
DOMContentLoaded
事件發(fā)生在 document
對(duì)象上。
我們必須使用 addEventListener
來(lái)捕獲它:
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>
元素。
但是,它不會(huì)等待圖片加載。因此,alert
顯示其大小為零。
乍一看,DOMContentLoaded
事件非常簡(jiǎn)單。DOM 樹(shù)準(zhǔn)備就緒 —— 這是它的觸發(fā)條件。它并沒(méi)有什么特別之處。
當(dāng)瀏覽器處理一個(gè) HTML 文檔,并在文檔中遇到 <script>
標(biāo)簽時(shí),就會(huì)在繼續(xù)構(gòu)建 DOM 之前運(yùn)行它。這是一種防范措施,因?yàn)槟_本可能想要修改 DOM,甚至對(duì)其執(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>
在上面這個(gè)例子中,我們首先會(huì)看到 “Library loaded…”,然后才會(huì)看到 “DOM ready!”(所有腳本都已經(jīng)執(zhí)行結(jié)束)。
不會(huì)阻塞 ?
DOMContentLoaded
? 的腳本此規(guī)則有兩個(gè)例外:
- 具有 ?
async
? 特性(attribute)的腳本不會(huì)阻塞 ?DOMContentLoaded
?,稍后 我們會(huì)講到。- 使用 ?
document.createElement('script')
? 動(dòng)態(tài)生成并添加到網(wǎng)頁(yè)的腳本也不會(huì)阻塞 ?DOMContentLoaded
?。
外部樣式表不會(huì)影響 DOM,因此 DOMContentLoaded
不會(huì)等待它們。
但這里有一個(gè)陷阱。如果在樣式后面有一個(gè)腳本,那么該腳本必須等待樣式表加載完成:
<link type="text/css" rel="stylesheet" href="style.css">
<script>
// 在樣式表加載完成之前,腳本都不會(huì)執(zhí)行
alert(getComputedStyle(document.body).marginTop);
</script>
原因是,腳本可能想要獲取元素的坐標(biāo)和其他與樣式相關(guān)的屬性,如上例所示。因此,它必須等待樣式加載完成。
當(dāng) DOMContentLoaded
等待腳本時(shí),它現(xiàn)在也在等待腳本前面的樣式。
Firefox,Chrome 和 Opera 都會(huì)在 DOMContentLoaded
中自動(dòng)填充表單。
例如,如果頁(yè)面有一個(gè)帶有登錄名和密碼的表單,并且瀏覽器記住了這些值,那么在 DOMContentLoaded
上,瀏覽器會(huì)嘗試自動(dòng)填充它們(如果得到了用戶(hù)允許)。
因此,如果 DOMContentLoaded
被需要加載很長(zhǎng)時(shí)間的腳本延遲觸發(fā),那么自動(dòng)填充也會(huì)等待。你可能在某些網(wǎng)站上看到過(guò)(如果你使用瀏覽器自動(dòng)填充)—— 登錄名/密碼字段不會(huì)立即自動(dòng)填充,而是在頁(yè)面被完全加載前會(huì)延遲填充。這實(shí)際上是 DOMContentLoaded
事件之前的延遲。
當(dāng)整個(gè)頁(yè)面,包括樣式、圖片和其他資源被加載完成時(shí),會(huì)觸發(fā) window
對(duì)象上的 load
事件??梢酝ㄟ^(guò) onload
屬性獲取此事件。
下面的這個(gè)示例正確顯示了圖片大小,因?yàn)?nbsp;window.onload
會(huì)等待所有圖片加載完畢:
<script>
window.onload = function() { // 也可以用 window.addEventListener('load', (event) => {
alert('Page loaded');
// 此時(shí)圖片已經(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" >
當(dāng)訪(fǎng)問(wèn)者離開(kāi)頁(yè)面時(shí),window
對(duì)象上的 unload
事件就會(huì)被觸發(fā)。我們可以在那里做一些不涉及延遲的操作,例如關(guān)閉相關(guān)的彈出窗口。
有一個(gè)值得注意的特殊情況是發(fā)送分析數(shù)據(jù)。
假設(shè)我們收集有關(guān)頁(yè)面使用情況的數(shù)據(jù):鼠標(biāo)點(diǎn)擊,滾動(dòng),被查看的頁(yè)面區(qū)域等。
自然地,當(dāng)用戶(hù)要離開(kāi)的時(shí)候,我們希望通過(guò) unload
事件將數(shù)據(jù)保存到我們的服務(wù)器上。
有一個(gè)特殊的 navigator.sendBeacon(url, data)
方法可以滿(mǎn)足這種需求,詳見(jiàn)規(guī)范 https://w3c.github.io/beacon/。
它在后臺(tái)發(fā)送數(shù)據(jù),轉(zhuǎn)換到另外一個(gè)頁(yè)面不會(huì)有延遲:瀏覽器離開(kāi)頁(yè)面,但仍然在執(zhí)行 sendBeacon
。
使用方式如下:
let analyticsData = { /* 帶有收集的數(shù)據(jù)的對(duì)象 */ };
window.addEventListener("unload", function() {
navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
});
當(dāng) sendBeacon
請(qǐng)求完成時(shí),瀏覽器可能已經(jīng)離開(kāi)了文檔,所以就無(wú)法獲取服務(wù)器響應(yīng)(對(duì)于分析數(shù)據(jù)來(lái)說(shuō)通常為空)。
還有一個(gè) keep-alive
標(biāo)志,該標(biāo)志用于在 fetch 方法中為通用的網(wǎng)絡(luò)請(qǐng)求執(zhí)行此類(lèi)“離開(kāi)頁(yè)面后”的請(qǐng)求。你可以在 Fetch API 一章中找到更多相關(guān)信息。
如果我們要取消跳轉(zhuǎn)到另一頁(yè)面的操作,在這里做不到。但是我們可以使用另一個(gè)事件 —— onbeforeunload
。
如果訪(fǎng)問(wèn)者觸發(fā)了離開(kāi)頁(yè)面的導(dǎo)航(navigation)或試圖關(guān)閉窗口,beforeunload
處理程序?qū)⒁筮M(jìn)行更多確認(rèn)。
如果我們要取消事件,瀏覽器會(huì)詢(xún)問(wèn)用戶(hù)是否確定。
你可以通過(guò)運(yùn)行下面這段代碼,然后重新加載頁(yè)面來(lái)進(jìn)行嘗試:
window.onbeforeunload = function() {
return false;
};
由于歷史原因,返回非空字符串也被視為取消事件。在以前,瀏覽器曾經(jīng)將其顯示為消息,但是根據(jù) 現(xiàn)代規(guī)范 所述,它們不應(yīng)該這樣。
這里有個(gè)例子:
window.onbeforeunload = function() {
return "有未保存的值。確認(rèn)要離開(kāi)嗎?";
};
它的行為已經(jīng)改變了,因?yàn)橛行┱鹃L(zhǎng)通過(guò)顯示誤導(dǎo)性和惡意信息濫用了此事件處理程序。所以,目前一些舊的瀏覽器可能仍將其顯示為消息,但除此之外 —— 無(wú)法自定義顯示給用戶(hù)的消息。
?
event.preventDefault()
? 在 ?beforeunload
? 處理程序中不起作用這聽(tīng)起來(lái)可能很奇怪,但大多數(shù)瀏覽器都會(huì)忽略
event.preventDefault()
。
這意味著,以下代碼可能不起作用:
window.addEventListener("beforeunload", (event) => { // 不起作用,所以這個(gè)事件處理程序沒(méi)做任何事兒 event.preventDefault(); });
相反,在這樣的處理程序中,應(yīng)該將
event.returnValue
設(shè)置為一個(gè)字符串,以獲得類(lèi)似于上面代碼的結(jié)果:
window.addEventListener("beforeunload", (event) => { // 起作用,與在 window.onbeforeunload 中 return 值的效果是一樣的 event.returnValue = "有未保存的值。確認(rèn)要離開(kāi)嗎?"; });
如果我們?cè)谖臋n加載完成之后設(shè)置 DOMContentLoaded
事件處理程序,會(huì)發(fā)生什么?
很自然地,它永遠(yuǎn)不會(huì)運(yùn)行。
在某些情況下,我們不確定文檔是否已經(jīng)準(zhǔn)備就緒。我們希望我們的函數(shù)在 DOM 加載完成時(shí)執(zhí)行,無(wú)論現(xiàn)在還是以后。
document.readyState
屬性可以為我們提供當(dāng)前加載狀態(tài)的信息。
它有 3 個(gè)可能值:
loading
? —— 文檔正在被加載。interactive
? —— 文檔被全部讀取。complete
? —— 文檔被全部讀取,并且所有資源(例如圖片等)都已加載完成。所以,我們可以檢查 document.readyState
并設(shè)置一個(gè)處理程序,或在代碼準(zhǔn)備就緒時(shí)立即執(zhí)行它。
像這樣:
function work() { /*...*/ }
if (document.readyState == 'loading') {
// 仍在加載,等待事件
document.addEventListener('DOMContentLoaded', work);
} else {
// DOM 已就緒!
work();
}
還有一個(gè) readystatechange
事件,會(huì)在狀態(tài)發(fā)生改變時(shí)觸發(fā),因此我們可以打印所有這些狀態(tài),就像這樣:
// 當(dāng)前狀態(tài)
console.log(document.readyState);
// 狀態(tài)改變時(shí)打印它
document.addEventListener('readystatechange', () => console.log(document.readyState));
readystatechange
事件是跟蹤文檔加載狀態(tài)的另一種機(jī)制,它很早就存在了。現(xiàn)在則很少被使用。
但是為了完整起見(jiàn),讓我們看看完整的事件流。
這是一個(gè)帶有 <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>
此示例運(yùn)行 在 sandbox 中。
典型輸出:
方括號(hào)中的數(shù)字表示發(fā)生這種情況的大致時(shí)間。標(biāo)有相同數(shù)字的事件幾乎是同時(shí)發(fā)生的(± 幾毫秒)。
DOMContentLoaded
? 之前,?document.readyState
? 會(huì)立即變成 ?interactive
?。它們倆的意義實(shí)際上是相同的。iframe
? 和 ?img
?)都加載完成后,?document.readyState
? 變成 ?complete
?。這里我們可以發(fā)現(xiàn),它與 ?img.onload
?(?img
? 是最后一個(gè)資源)和 ?window.onload
? 幾乎同時(shí)發(fā)生。轉(zhuǎn)換到 ?complete
? 狀態(tài)的意義與 ?window.onload
? 相同。區(qū)別在于 ?window.onload
? 始終在所有其他 ?load
? 處理程序之后運(yùn)行。頁(yè)面生命周期事件:
document
? 上的 ?DOMContentLoaded
? 事件就會(huì)被觸發(fā)。在這個(gè)階段,我們可以將 JavaScript 應(yīng)用于元素。<script>...</script>
? 或 ?<script src="..."></script>
? 之類(lèi)的腳本會(huì)阻塞 ?DOMContentLoaded
?,瀏覽器將等待它們執(zhí)行結(jié)束。window
? 上的 ?load
? 事件就會(huì)被觸發(fā)。我們很少使用它,因?yàn)橥ǔo(wú)需等待那么長(zhǎng)時(shí)間。window
? 上的 ?beforeunload
? 事件就會(huì)被觸發(fā)。如果我們?nèi)∠@個(gè)事件,瀏覽器就會(huì)詢(xún)問(wèn)我們是否真的要離開(kāi)(例如,我們有未保存的更改)。window
? 上的 ?unload
? 事件就會(huì)被觸發(fā)。在處理程序中,我們只能執(zhí)行不涉及延遲或詢(xún)問(wèn)用戶(hù)的簡(jiǎn)單操作。正是由于這個(gè)限制,它很少被使用。我們可以使用 ?navigator.sendBeacon
? 來(lái)發(fā)送網(wǎng)絡(luò)請(qǐng)求。document.readyState
? 是文檔的當(dāng)前狀態(tài),可以在 ?readystatechange
? 事件中跟蹤狀態(tài)更改:loading
? —— 文檔正在被加載。interactive
? —— 文檔已被解析完成,與 ?DOMContentLoaded
? 幾乎同時(shí)發(fā)生,但是在 ?DOMContentLoaded
? 之前發(fā)生。complete
? —— 文檔和資源均已加載完成,與 ?window.onload
? 幾乎同時(shí)發(fā)生,但是在 ?window.onload
? 之前發(fā)生。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: