原文地址:http://javascript.info/onload...javascript
HTML頁面的生命週期有如下三個重要事件:css
DOMContentLoaded
— 瀏覽器已經徹底加載了HTML,DOM樹已經構建完畢,可是像是 <img>
和樣式表等外部資源可能並無下載完畢。load
— 瀏覽器已經加載了全部的資源(圖像,樣式表等)。beforeunload/unload
-- 當用戶離開頁面的時候觸發。每一個事件都有特定的用途html
DOMContentLoaded
-- DOM加載完畢,因此js能夠訪問全部DOM節點,初始化界面。load
-- 附加資源已經加載完畢,能夠在此事件觸發時得到圖像的大小(若是沒有被在HTML/CSS中指定)beforeunload/unload
-- 用戶正在離開頁面:能夠詢問用戶是否保存了更改以及是否肯定要離開頁面。來看一下每一個事件的細節。java
DOMContentLoaded
由 document
對象觸發。瀏覽器
咱們使用 addEventListener
來監聽它:緩存
document.addEventListener("DOMContentLoaded", ready);
舉個例子安全
<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">
在這個例子中 DOMContentLoaded
在document加載完成後就被觸發,無需等待其餘資源的載入,因此alert
輸出的圖像的大小爲0。網絡
這麼看來DOMContentLoaded
彷佛很簡單,DOM樹構建完畢以後就運行該事件,不過其實存在一些陷阱。框架
當瀏覽器在解析HTML頁面時遇到了 <script>...</script>
標籤,將沒法繼續構建DOM樹(譯註:UI渲染線程與JS引擎是互斥的,當JS引擎執行時UI線程會被掛起),必須當即執行腳本。因此 DOMContentLoaded
有可能在全部腳本執行完畢後觸發。less
外部腳本(帶src
的)的加載和解析也會暫停DOM樹構建,因此 DOMContentLoaded
也會等待外部腳本。
不過有兩個例外是帶async
和defer
的外部腳本,他們告訴瀏覽器繼續解析而不須要等待腳本的執行,因此用戶能夠在腳本加載完成前能夠看到頁面,有較好的用戶體驗。
async
和defer
屬性僅僅對外部腳本起做用,而且他們在src
不存在時會被自動忽略。
它們都告訴瀏覽器繼續處理頁面上的內容,而在後臺加載腳本,而後在腳本加載完畢後再執行。因此腳本不會阻塞DOM樹的構建和頁面的渲染。
(譯註:其實這裏是不對的,帶有async
和defer
的腳本的下載是和HTML的下載與解析是異步的,可是js的執行必定是和UI線程是互斥的,像下面這張圖所示,async
在下載完畢後的執行會阻塞HTML的解析)
他們有兩處不一樣:
async |
defer |
|
---|---|---|
順序 | 帶有async 的腳本是優先執行先加載完的腳本,他們在頁面中的順序並不影響他們執行的順序。 |
帶有defer 的腳本按照他們在頁面中出現的順序依次執行。 |
DOMContentLoaded |
帶有async 的腳本也許會在頁面沒有徹底下載完以前就加載,這種狀況會在腳本很小或本緩存,而且頁面很大的狀況下發生。 |
帶有defer 的腳本會在頁面加載和解析完畢後執行,恰好在 DOMContentLoaded 以前執行。 |
因此async
用在那些徹底不依賴其餘腳本的腳本上。
### DOMContentLoaded and styles External style sheets don't affect DOM, and so `DOMContentLoaded` does not wait for them. 外部樣式表並不會影響DOM,因此`DOMContentLoaded`並不會被他們阻塞。 But there's a pitfall: if we have a script after the style, then that script must wait for the stylesheet to execute: 不過仍然有一個陷阱:若是在樣式後面有一個內聯腳本,那麼腳本必須等待樣式先加載完。 <link type="text/css" rel="stylesheet" href="style.css"> <script> // the script doesn't not execute until the stylesheet is loaded // 腳本直到樣式表加載完畢後纔會執行。 alert(getComputedStyle(document.body).marginTop); </script>
發生這種事的緣由是腳本也許會像上面的例子中所示,去獲得一些元素的座標或者基於樣式的屬性。因此他們天然要等到樣式加載完畢才能夠執行。
DOMContentLoaded
須要等待腳本的執行,腳本又須要等待樣式的加載。
Firefox, Chrome和Opera會在DOMContentLoaded
執行時自動補全表單。
例如,若是頁面有登陸的界面,瀏覽器記住了該頁面的用戶名和密碼,那麼在 DOMContentLoaded
運行的時候瀏覽器會試圖自動補全表單(若是用戶設置容許)。
因此若是DOMContentLoaded
被一個須要長時間執行的腳本阻塞,那麼自動補全也會等待。你也許見過某些網站(若是你的瀏覽器開啓了自動補全)—— 瀏覽器並不會馬上補全登陸項,而是等到整個頁面加載完畢後才填充。這就是由於在等待DOMContentLoaded
事件。
使用帶async
和defer
的腳本的一個好處就是,他們不會阻塞DOMContentLoaded
和瀏覽器自動補全。(譯註:其實執行仍是會阻塞的)
window
對象上的onload
事件在全部文件包括樣式表,圖片和其餘資源下載完畢後觸發。
下面的例子正確檢測了圖片的大小,由於window.onload
會等待全部圖片的加載。
<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
對象上的unload
事件會被觸發,咱們能夠作一些不存在延遲的事情,好比關閉彈出的窗口,但是咱們沒法阻止用戶轉移到另外一個頁面上。
因此咱們須要使用另外一個事件 — onbeforeunload
。
若是用戶即將離開頁面或者關閉窗口時,beforeunload
事件將會被觸發以進行額外的確認。
瀏覽器將顯示返回的字符串,舉個例子:
window.onbeforeunload = function() { return "There are unsaved changes. Leave now?"; };
有些瀏覽器像Chrome和火狐會忽略返回的字符串取而代之顯示瀏覽器自身的文本,這是爲了安全考慮,來保證用戶不受到錯誤信息的誤導。
若是咱們在整個頁面加載完畢後設置DOMContentLoaded
會發生什麼呢?
啥也沒有,DOMContentLoaded
不會被觸發。
有一些狀況咱們沒法肯定頁面上是否已經加載完畢,好比一個帶有async
的外部腳本的加載和執行是異步的(注:執行並非異步的-_-)。在不一樣的網絡情況下,腳本有多是在頁面加載完畢後執行也有多是在頁面加載完畢前執行,咱們沒法肯定。因此咱們須要知道頁面加載的情況。
document.readyState
屬性給了咱們加載的信息,有三個可能的值:
loading
加載 - document仍在加載。interactive
互動 - 文檔已經完成加載,文檔已被解析,可是諸如圖像,樣式表和框架之類的子資源仍在加載。complete
- 文檔和全部子資源已完成加載。狀態表示 load
事件即將被觸發。因此咱們能夠檢查 document.readyState
的狀態,若是沒有就緒能夠選擇掛載事件,若是已經就緒了就能夠直接當即執行。
像這樣:
function work() { /*...*/ } if (document.readyState == 'loading') { document.addEventListener('DOMContentLoaded', work); } else { work(); }
每當文檔的加載狀態改變的時候就有一個readystatechange
事件被觸發,因此咱們能夠打印全部的狀態。
// current state console.log(document.readyState); // print state changes document.addEventListener('readystatechange', () => console.log(document.readyState));
readystatechange
是追蹤頁面加載的一個可選的方法,很早以前就已經出現了。不過如今不多被使用了,爲了保持完整性仍是介紹一下它。
readystatechange
的在各個事件中的執行順序又是如何呢?
<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>
輸出以下:
方括號中的數字表示他們發生的時間,真實的發生時間會更晚一點,不過相同數字的時間能夠認爲是在同一時刻被按順序觸發(偏差在幾毫秒以內)
document.readyState
在 DOMContentLoaded
前一刻變爲interactive
,這兩個事件能夠認爲是同時發生。document.readyState
在全部資源加載完畢後(包括iframe
和img
)變成complete
,咱們能夠看到complete
、 img.onload
和window.onload
幾乎同時發生,區別就是window.onload
在全部其餘的load
事件以後執行。頁面事件的生命週期:
DOMContentLoaded
事件在DOM樹構建完畢後被觸發,咱們能夠在這個階段使用js去訪問元素。
async
和defer
的腳本可能尚未執行。load
事件在頁面全部資源被加載完畢後觸發,一般咱們不會用到這個事件,由於咱們不須要等那麼久。beforeunload
在用戶即將離開頁面時觸發,它返回一個字符串,瀏覽器會向用戶展現並詢問這個字符串以肯定是否離開。unload
在用戶已經離開時觸發,咱們在這個階段僅能夠作一些沒有延遲的操做,因爲種種限制,不多被使用。document.readyState
表徵頁面的加載狀態,能夠在readystatechange
中追蹤頁面的變化狀態:
loading
— 頁面正在加載中。interactive
-- 頁面解析完畢,時間上和 DOMContentLoaded
同時發生,不過順序在它以前。complete
-- 頁面上的資源都已加載完畢,時間上和window.onload
同時發生,不過順序在他以前。