頁面生命周期

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

HTML頁面的生命周期有以下三個(gè)重要事件:

  • DOMContentLoaded — 瀏覽器已經(jīng)完全加載了HTML,DOM樹已經(jīng)構(gòu)建完畢,但是像是 <img> 和樣式表等外部資源可能并沒有下載完畢。
  • load — 瀏覽器已經(jīng)加載了所有的資源(圖像,樣式表等)。
  • beforeunload/unload -- 當(dāng)用戶離開頁面的時(shí)候觸發(fā)。

每個(gè)事件都有特定的用途

  • DOMContentLoaded -- DOM加載完畢,所以js可以訪問所有DOM節(jié)點(diǎn),初始化界面。
  • load -- 附加資源已經(jīng)加載完畢,可以在此事件觸發(fā)時(shí)獲得圖像的大小(如果沒有被在HTML/CSS中指定)
  • beforeunload/unload -- 用戶正在離開頁面:可以詢問用戶是否保存了更改以及是否確定要離開頁面。

來看一下每個(gè)事件的細(xì)節(jié)。

DOMContentLoaded

DOMContentLoadeddocument 對(duì)象觸發(fā)。

我們使用 addEventListener 來監(jiān)聽它:

document.addEventListener("DOMContentLoaded", ready);

舉個(gè)例子

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

    // image is not yet loaded (unless 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">

在這個(gè)例子中 DOMContentLoaded在document加載完成后就被觸發(fā),無需等待其他資源的載入,所以alert輸出的圖像的大小為0。

這么看來DOMContentLoaded 似乎很簡(jiǎn)單,DOM樹構(gòu)建完畢之后就運(yùn)行該事件,不過其實(shí)存在一些陷阱。

DOMContentLoaded 和腳本

當(dāng)瀏覽器在解析HTML頁面時(shí)遇到了 <script>...</script> 標(biāo)簽,將無法繼續(xù)構(gòu)建DOM樹(譯注:UI渲染線程與JS引擎是互斥的,當(dāng)JS引擎執(zhí)行時(shí)UI線程會(huì)被掛起),必須立即執(zhí)行腳本。所以 DOMContentLoaded 有可能在所有腳本執(zhí)行完畢后觸發(fā)。

外部腳本(帶src的)的加載和解析也會(huì)暫停DOM樹構(gòu)建,所以 DOMContentLoaded 也會(huì)等待外部腳本。

不過有兩個(gè)例外是帶asyncdefer的外部腳本,他們告訴瀏覽器繼續(xù)解析而不需要等待腳本的執(zhí)行,所以用戶可以在腳本加載完成前可以看到頁面,有較好的用戶體驗(yàn)。

asyncdefer屬性僅僅對(duì)外部腳本起作用,并且他們?cè)?code>src不存在時(shí)會(huì)被自動(dòng)忽略。

它們都告訴瀏覽器繼續(xù)處理頁面上的內(nèi)容,而在后臺(tái)加載腳本,然后在腳本加載完畢后再執(zhí)行。所以腳本不會(huì)阻塞DOM樹的構(gòu)建和頁面的渲染。

(譯注:其實(shí)這里是不對(duì)的,帶有asyncdefer的腳本的下載是和HTML的下載與解析是異步的,但是js的執(zhí)行一定是和UI線程是互斥的,像下面這張圖所示,async在下載完畢后的執(zhí)行會(huì)阻塞HTML的解析)

image.png

他們有兩處不同:

async defer
順序 帶有async的腳本是優(yōu)先執(zhí)行先加載完的腳本,他們?cè)陧撁嬷械捻樞虿⒉挥绊懰麄儓?zhí)行的順序。 帶有defer的腳本按照他們?cè)陧撁嬷谐霈F(xiàn)的順序依次執(zhí)行。
DOMContentLoaded 帶有async的腳本也許會(huì)在頁面沒有完全下載完之前就加載,這種情況會(huì)在腳本很小或本緩存,并且頁面很大的情況下發(fā)生。 帶有defer的腳本會(huì)在頁面加載和解析完畢后執(zhí)行,剛好在DOMContentLoaded之前執(zhí)行。

所以async用在那些完全不依賴其他腳本的腳本上。

### DOMContentLoaded and styles

External style sheets don't affect DOM, and so `DOMContentLoaded` does not wait for them.
外部樣式表并不會(huì)影響DOM,所以`DOMContentLoaded`并不會(huì)被他們阻塞。
But there's a pitfall: if we have a script after the style, then that script must wait for the stylesheet to execute:
不過仍然有一個(gè)陷阱:如果在樣式后面有一個(gè)內(nèi)聯(lián)腳本,那么腳本必須等待樣式先加載完。

<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // the script doesn't not execute until the stylesheet is loaded
  // 腳本直到樣式表加載完畢后才會(huì)執(zhí)行。
  alert(getComputedStyle(document.body).marginTop);
</script>

發(fā)生這種事的原因是腳本也許會(huì)像上面的例子中所示,去得到一些元素的坐標(biāo)或者基于樣式的屬性。所以他們自然要等到樣式加載完畢才可以執(zhí)行。

DOMContentLoaded需要等待腳本的執(zhí)行,腳本又需要等待樣式的加載。

瀏覽器的自動(dòng)補(bǔ)全

Firefox, Chrome和Opera會(huì)在DOMContentLoaded執(zhí)行時(shí)自動(dòng)補(bǔ)全表單。

例如,如果頁面有登錄的界面,瀏覽器記住了該頁面的用戶名和密碼,那么在 DOMContentLoaded運(yùn)行的時(shí)候?yàn)g覽器會(huì)試圖自動(dòng)補(bǔ)全表單(如果用戶設(shè)置允許)。

所以如果DOMContentLoaded被一個(gè)需要長(zhǎng)時(shí)間執(zhí)行的腳本阻塞,那么自動(dòng)補(bǔ)全也會(huì)等待。你也許見過某些網(wǎng)站(如果你的瀏覽器開啟了自動(dòng)補(bǔ)全)—— 瀏覽器并不會(huì)立刻補(bǔ)全登錄項(xiàng),而是等到整個(gè)頁面加載完畢后才填充。這就是因?yàn)樵诘却?code>DOMContentLoaded事件。

使用帶asyncdefer的腳本的一個(gè)好處就是,他們不會(huì)阻塞DOMContentLoaded和瀏覽器自動(dòng)補(bǔ)全。(譯注:其實(shí)執(zhí)行還是會(huì)阻塞的)

window.onload

window對(duì)象上的onload事件在所有文件包括樣式表,圖片和其他資源下載完畢后觸發(fā)。

下面的例子正確檢測(cè)了圖片的大小,因?yàn)?code>window.onload會(huì)等待所有圖片的加載。

<script>
  window.onload = function() {
    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

用戶離開頁面的時(shí)候,window對(duì)象上的unload事件會(huì)被觸發(fā),我們可以做一些不存在延遲的事情,比如關(guān)閉彈出的窗口,可是我們無法阻止用戶轉(zhuǎn)移到另一個(gè)頁面上。

所以我們需要使用另一個(gè)事件 — onbeforeunload。

window.onbeforeunload

如果用戶即將離開頁面或者關(guān)閉窗口時(shí),beforeunload事件將會(huì)被觸發(fā)以進(jìn)行額外的確認(rèn)。

瀏覽器將顯示返回的字符串,舉個(gè)例子:

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

有些瀏覽器像Chrome和火狐會(huì)忽略返回的字符串取而代之顯示瀏覽器自身的文本,這是為了安全考慮,來保證用戶不受到錯(cuò)誤信息的誤導(dǎo)。

readyState

如果我們?cè)谡麄€(gè)頁面加載完畢后設(shè)置DOMContentLoaded會(huì)發(fā)生什么呢?

啥也沒有,DOMContentLoaded不會(huì)被觸發(fā)。

有一些情況我們無法確定頁面上是否已經(jīng)加載完畢,比如一個(gè)帶有async的外部腳本的加載和執(zhí)行是異步的(注:執(zhí)行并不是異步的-_-)。在不同的網(wǎng)絡(luò)狀況下,腳本有可能是在頁面加載完畢后執(zhí)行也有可能是在頁面加載完畢前執(zhí)行,我們無法確定。所以我們需要知道頁面加載的狀況。

document.readyState屬性給了我們加載的信息,有三個(gè)可能的值:

  • loading 加載 - document仍在加載。
  • interactive 互動(dòng) - 文檔已經(jīng)完成加載,文檔已被解析,但是諸如圖像,樣式表和框架之類的子資源仍在加載。
  • complete - 文檔和所有子資源已完成加載。狀態(tài)表示 load 事件即將被觸發(fā)。

所以我們可以檢查 document.readyState 的狀態(tài),如果沒有就緒可以選擇掛載事件,如果已經(jīng)就緒了就可以直接立即執(zhí)行。

像這樣:

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

if (document.readyState == 'loading') {
  document.addEventListener('DOMContentLoaded', work);
} else {
  work();
}

每當(dāng)文檔的加載狀態(tài)改變的時(shí)候就有一個(gè)readystatechange事件被觸發(fā),所以我們可以打印所有的狀態(tài)。

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

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

readystatechange 是追蹤頁面加載的一個(gè)可選的方法,很早之前就已經(jīng)出現(xiàn)了。不過現(xiàn)在很少被使用了,為了保持完整性還是介紹一下它。

readystatechange的在各個(gè)事件中的執(zhí)行順序又是如何呢?

<script>
  function log(text) { /* output the time and message */ }
  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>

輸出如下:

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

方括號(hào)中的數(shù)字表示他們發(fā)生的時(shí)間,真實(shí)的發(fā)生時(shí)間會(huì)更晚一點(diǎn),不過相同數(shù)字的時(shí)間可以認(rèn)為是在同一時(shí)刻被按順序觸發(fā)(誤差在幾毫秒之內(nèi))

  • document.readyStateDOMContentLoaded前一刻變?yōu)?code>interactive,這兩個(gè)事件可以認(rèn)為是同時(shí)發(fā)生。
  • document.readyState 在所有資源加載完畢后(包括iframeimg)變成complete,我們可以看到complete、 img.onloadwindow.onload幾乎同時(shí)發(fā)生,區(qū)別就是window.onload在所有其他的load事件之后執(zhí)行。

總結(jié)

頁面事件的生命周期:

  • DOMContentLoaded事件在DOM樹構(gòu)建完畢后被觸發(fā),我們可以在這個(gè)階段使用js去訪問元素。

    • asyncdefer的腳本可能還沒有執(zhí)行。
    • 圖片及其他資源文件可能還在下載中。
  • load事件在頁面所有資源被加載完畢后觸發(fā),通常我們不會(huì)用到這個(gè)事件,因?yàn)槲覀儾恍枰饶敲淳谩?/p>

  • beforeunload在用戶即將離開頁面時(shí)觸發(fā),它返回一個(gè)字符串,瀏覽器會(huì)向用戶展示并詢問這個(gè)字符串以確定是否離開。

  • unload在用戶已經(jīng)離開時(shí)觸發(fā),我們?cè)谶@個(gè)階段僅可以做一些沒有延遲的操作,由于種種限制,很少被使用。

  • document.readyState表征頁面的加載狀態(tài),可以在readystatechange中追蹤頁面的變化狀態(tài):

    • loading — 頁面正在加載中。
    • interactive -- 頁面解析完畢,時(shí)間上和 DOMContentLoaded同時(shí)發(fā)生,不過順序在它之前。
    • complete -- 頁面上的資源都已加載完畢,時(shí)間上和window.onload同時(shí)發(fā)生,不過順序在他之前。

原文地址:http://javascript.info/onload...
原文作者:fi3ework

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容