頁面的加載和渲染過程
瀏覽器通過
HTTP
協議請求服務器,獲取HMTL
文檔并開始從上到下解析,構建DOM
;在構建
DOM
過程中,如果遇到外聯的樣式聲明和腳本聲明,則暫停文檔解析,創建新的網絡連接,并開始下載樣式文件和腳本文件;樣式文件下載完成后,構建
CSSDOM
;腳本文件下載完成后,解釋并執行,然后繼續解析文檔構建DOM
;完成文檔解析后,將
DOM
和CSSDOM
進行關聯和映射,最后將視圖渲染到瀏覽器窗口 。
在這個過程中,腳本文件的下載和執行是與文檔解析同步進行,也就是說,它會阻塞文檔的解析,如果控制得不好,在用戶體驗上就會造成一定程度的影響。
<script src="script.js"></script>
沒有 defer 或 async,瀏覽器會立即加載并執行指定的腳本,“立即”指的是在渲染該 script 標簽之下的文檔元素之前,也就是說不等待后續載入的文檔元素,讀到就加載并執行。
<script async src="script.js"></script>
有 async,加載和渲染后續文檔元素的過程將和 script.js 的加載與執行并行進行(異步)。
<script defer src="myscript.js"></script>
有 defer,加載后續文檔元素的過程將和 script.js 的加載并行進行(異步),但是 script.js 的執行要在所有元素解析完成之后,DOMContentLoaded 事件觸發之前完成。
defer
用于開啟新的線程下載腳本文件,并使腳本在文檔解析完成后執行。
這個屬性的用途是表明腳本在執行時不會影響頁面的構造。也就是說,腳本會被延遲到整個頁面都解析完畢后再運行。因此,在<script>
元素中設置defer
屬性,相當于告訴瀏覽器立即下載,但延遲執行。
HTML5
規范要求腳本按照它們出現的先后順序執行,因此第一個延遲腳本會先于第二個延遲腳本執行,而這兩個腳本會先于DOMContentLoaded
事件執行。在現實當中,延遲腳本并不一定會按照順序執行,也不一定會在DOMContentLoad
時間觸發前執行,因此最好只包含一個延遲腳本。
async
HTML5新增屬性,用于異步下載腳本文件,下載完畢立即解釋執行代碼。
這個屬性與defer
類似,都用于改變處理腳本的行為。同樣與defer
類似,async
只適用于外部腳本文件,并告訴瀏覽器立即下載文件。但與defer
不同的是,標記為async
的腳本并不保證按照它們的先后順序執行。
第二個腳本文件可能會在第一個腳本文件之前執行。因此確保兩者之間互不依賴非常重要。指定async
屬性的目的是不讓頁面等待兩個腳本下載和執行,從而異步加載頁面其他內容。
圖片來源:討論區地址
藍色線代表網絡讀取,紅色線代表執行時間,這倆都是針對腳本的;綠色線代表 HTML
解析。
也就是說async
是亂序的,而defer
是順序執行,這也就決定了async
比較適用于百度分析或者谷歌分析這類不依賴其他腳本的庫。從圖中可以看到一個普通的<script>
標簽的加載和解析都是同步的,會阻塞DOM
的渲染,這也就是我們經常會把<script>
寫在<body>
底部的原因之一,為了防止加載資源而導致的長時間的白屏,另一個原因是js
可能會進行DOM
操作,所以要在DOM
全部渲染完后再執行。