頁面的加載和渲染過程
瀏覽器通過
HTTP協(xié)議請求服務(wù)器,獲取HMTL文檔并開始從上到下解析,構(gòu)建DOM;在構(gòu)建
DOM過程中,如果遇到外聯(lián)的樣式聲明和腳本聲明,則暫停文檔解析,創(chuàng)建新的網(wǎng)絡(luò)連接,并開始下載樣式文件和腳本文件;樣式文件下載完成后,構(gòu)建
CSSDOM;腳本文件下載完成后,解釋并執(zhí)行,然后繼續(xù)解析文檔構(gòu)建DOM;完成文檔解析后,將
DOM和CSSDOM進(jìn)行關(guān)聯(lián)和映射,最后將視圖渲染到瀏覽器窗口 。
在這個(gè)過程中,腳本文件的下載和執(zhí)行是與文檔解析同步進(jìn)行,也就是說,它會(huì)阻塞文檔的解析,如果控制得不好,在用戶體驗(yàn)上就會(huì)造成一定程度的影響。
<script src="script.js"></script>
沒有 defer 或 async,瀏覽器會(huì)立即加載并執(zhí)行指定的腳本,“立即”指的是在渲染該 script 標(biāo)簽之下的文檔元素之前,也就是說不等待后續(xù)載入的文檔元素,讀到就加載并執(zhí)行。
<script async src="script.js"></script>
有 async,加載和渲染后續(xù)文檔元素的過程將和 script.js 的加載與執(zhí)行并行進(jìn)行(異步)。
<script defer src="myscript.js"></script>
有 defer,加載后續(xù)文檔元素的過程將和 script.js 的加載并行進(jìn)行(異步),但是 script.js 的執(zhí)行要在所有元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前完成。

defer
用于開啟新的線程下載腳本文件,并使腳本在文檔解析完成后執(zhí)行。
這個(gè)屬性的用途是表明腳本在執(zhí)行時(shí)不會(huì)影響頁面的構(gòu)造。也就是說,腳本會(huì)被延遲到整個(gè)頁面都解析完畢后再運(yùn)行。因此,在<script>元素中設(shè)置defer屬性,相當(dāng)于告訴瀏覽器立即下載,但延遲執(zhí)行。
HTML5規(guī)范要求腳本按照它們出現(xiàn)的先后順序執(zhí)行,因此第一個(gè)延遲腳本會(huì)先于第二個(gè)延遲腳本執(zhí)行,而這兩個(gè)腳本會(huì)先于DOMContentLoaded事件執(zhí)行。在現(xiàn)實(shí)當(dāng)中,延遲腳本并不一定會(huì)按照順序執(zhí)行,也不一定會(huì)在DOMContentLoad時(shí)間觸發(fā)前執(zhí)行,因此最好只包含一個(gè)延遲腳本。
async
HTML5新增屬性,用于異步下載腳本文件,下載完畢立即解釋執(zhí)行代碼。
這個(gè)屬性與defer類似,都用于改變處理腳本的行為。同樣與defer類似,async只適用于外部腳本文件,并告訴瀏覽器立即下載文件。但與defer不同的是,標(biāo)記為async的腳本并不保證按照它們的先后順序執(zhí)行。
第二個(gè)腳本文件可能會(huì)在第一個(gè)腳本文件之前執(zhí)行。因此確保兩者之間互不依賴非常重要。指定async屬性的目的是不讓頁面等待兩個(gè)腳本下載和執(zhí)行,從而異步加載頁面其他內(nèi)容。

圖片來源:討論區(qū)地址
藍(lán)色線代表網(wǎng)絡(luò)讀取,紅色線代表執(zhí)行時(shí)間,這倆都是針對腳本的;綠色線代表 HTML 解析。
也就是說async是亂序的,而defer是順序執(zhí)行,這也就決定了async比較適用于百度分析或者谷歌分析這類不依賴其他腳本的庫。從圖中可以看到一個(gè)普通的<script>標(biāo)簽的加載和解析都是同步的,會(huì)阻塞DOM的渲染,這也就是我們經(jīng)常會(huì)把<script>寫在<body>底部的原因之一,為了防止加載資源而導(dǎo)致的長時(shí)間的白屏,另一個(gè)原因是js可能會(huì)進(jìn)行DOM操作,所以要在DOM全部渲染完后再執(zhí)行。
參考文章
defer和async的區(qū)別
淺談script標(biāo)簽的defer和async
詳解defer和async的原理及應(yīng)用