JS的(de)阻塞加載和(hé) defer 和(hé) async 屬性
JS具有阻塞特性,當浏覽器在執行js代碼時,不能同時做(zuò)其它事情,即<script>每次出現都會讓頁面等待腳本的(de)解析和(hé)執行(不論JS是內(nèi)嵌的(de)還是外鏈的(de)),JS代碼執行完成後,才繼續渲染頁面。
所有浏覽器在下載JS的(de)時候,會阻止一(yī)切其他活動,比如(rú)其他資源的(de)下載,內(nèi)容的(de)呈現等等。至到JS下載、解析、執行完畢後才開始繼續并行下載其他資源并呈現內(nèi)容。
有人會問:為(wèi)什麽JS不能像CSS、image一(yī)樣并行下載了?這裏需要簡單介紹一(yī)下浏覽器構造頁面的(de)原理(lǐ),
當浏覽器從服務器接收到了HTML文檔,并把HTML在內(nèi)存中轉換成DOM樹,在轉換的(de)過程中如(rú)果發現某個節點(node)上引用了CSS或者IMAGE,就會再發1個request去(qù)請求CSS或image,然後繼續執行下面的(de)轉換,而不需要等待request的(de)返回,當request返回後,隻需要把返回的(de)內(nèi)容放入到DOM樹中對應的(de)位置就OK。但當引用了JS的(de)時候,浏覽器發送1個js request就會一(yī)直等待該request的(de)返回。因為(wèi)浏覽器需要1個穩定的(de)DOM樹結構,而JS中很有可(kě)能有代碼直接改變了DOM樹結構,比如(rú)使用document.write 或 appendChild,甚至是直接使用的(de)location.href進行跳轉,浏覽器為(wèi)了防止出現JS修改DOM樹,需要重新構建DOM樹的(de)情況,所以就會阻塞其他的(de)下載和(hé)呈現.
阻塞下載圖:下圖是訪問blogjava首頁的(de)時間瀑布圖,可(kě)以看出來開始的(de)2個image都是并行下載的(de),而後面的(de)2個JS都是阻塞下載的(de)(1個1個下載)。
由于,JS的(de)這種阻塞特性,每次遇到<script>,頁面都必須停下來等待腳本下載并執行,這會停止頁面繪制,帶來不好的(de)用戶體驗。所以,有必要減少JS阻塞特性造成的(de)困擾。
1 優化腳本位置
HTML4規範中,<script>可(kě)以放在<head>或<body>中。你可(kě)能習慣性的(de)在<head>中放置多個外鏈JS、CSS,以求優先加載它們。浏覽器在繼續到<body>之前,不會渲染頁面,所以,把JS放在<head>中,會導緻延遲。為(wèi)了提高(gāo)用戶體驗,新一(yī)代浏覽器都支持并行下載JS,但是JS下載仍然會阻塞其它資源的(de)下載(eg.圖片)。盡管腳本的(de)下載過程并不會相互影響,但頁面仍然必須等待所有JS下載并執行完成才能繼續。顯見,所有<script>應該盡可(kě)能放到<body>的(de)底部,以減少對頁面下載的(de)影響。
注意:CSS文件本身是并行下載,不會阻塞頁面的(de)其他進程。但是,如(rú)果把一(yī)段內(nèi)嵌腳本放在引用外鏈CSS的(de)<link>之後會導緻頁面阻塞去(qù)等待CSS的(de)下載。這樣做(zuò)是為(wèi)了确保內(nèi)嵌腳本在執行時能夠獲得正确的(de)樣式信息。所以,最好不要把內(nèi)嵌腳本放在CSS的(de)<link>之後。
2 減少外鏈腳本數量以改善性能
原因很簡單,額外的(de)HTTP請求會帶來額外的(de)開銷,所以減少頁面中外鏈腳本的(de)數量,有助于改善性能。
3 使用無阻塞下載JS方法
無阻塞腳本的(de)秘訣在于,在頁面加載完成後才加載JS,即在window對象的(de)load事件觸發後在下載腳本。
4 使用<script>的(de)defer屬性(僅IE和(hé)Firefox3.5以上);
defer屬性指明本元素所含的(de)腳本不會修改DOM,因此代碼能安全的(de)延遲執行。defer屬性的(de)<script>,對應的(de)JS文件将在頁面解析到<script>時開始下載,但并不會執行,直到DOM加載完成,即onload事件觸發前被調用。當一(yī)個帶有defer屬性的(de)JS文件下載時,他不會阻塞浏覽器的(de)其它進程,因此這類文件可(kě)以與頁面中的(de)其他資源并行下載。
5 嵌入JS應該放在什麽位置
1、放在底部,雖然放在底部照樣會阻塞所有呈現,但不會阻塞資源下載。
2、如(rú)果嵌入JS放在head中,請把嵌入JS放在CSS頭部。
3、使用defer
4、不要在嵌入的(de)JS中調用運行時間較長(cháng)的(de)函數,如(rú)果一(yī)定要用,可(kě)以用setTimeout來調用
PS:很多網站喜歡在head中嵌入JS,并且習慣放在CSS後面,比如(rú)看到的(de)www.qq.com,當然也有很多網站是把JS放到CSS前面的(de),比如(rú)yahoo,google
js的(de)[defer]和(hé)[async]屬性
HTML4.01為(wèi)<script>定義了6個屬性,包括defer和(hé)async。defer和(hé)async都是可(kě)選的(de),且隻對外部腳本文件有效。
一(yī)、當浏覽器解析到script腳本,沒有defer或async時:
<script src="main.js"></script>
浏覽器會立即加載并執行指定的(de)腳本,“立即”指在渲染該script标簽之下的(de)文檔元素之前,也就是說不等待後續載入的(de)文檔元素,讀到就加載并執行。
二、當浏覽器解析到script腳本,有async時:
<script async src="main.js"></script>
浏覽器會立即下載腳本,但不妨礙頁面中的(de)其他操作,比如(rú)下載其他資源或等待加載其他腳本。加載和(hé)渲染後續文檔元素的(de)過程和(hé)main.js的(de)加載與執行并行進行(異步)。
async不保證按照腳本出現的(de)先後順序執行,因此,确保兩者之前互不依賴非常重要,指定async屬性的(de)目的(de)是不讓頁面等待兩個腳本的(de)下載和(hé)執行,從而異步加載頁面其他內(nèi)容,建議異步腳本不要在加載期間修改DOM。
異步腳本一(yī)定會在頁面的(de)load事件前執行,但可(kě)能會在DOMContentLoaded事件觸發之前或之後執行。支持異步腳本的(de)浏覽器有Firefox 3.6、Safari 5 和(hé)Chrome。
三、當浏覽器解析到script腳本,有defer時:
<script defer="defer" src="main1.js"></script><script defer="defer" src="main2.js"></script>
表示腳本會被延遲到文檔完全被解析和(hé)顯示之後再執行,加載後續文檔元素的(de)過程将和(hé)main.js的(de)加載并行進行(異步)。
HTML5規範要求腳本按照它們出現的(de)先後順序執行,因此第一(yī)個延遲腳本會先于第二個延遲腳本執行,而這兩個腳本會先于DOMContentLoaded事件。
在現實當中,延遲腳本并不一(yī)定會按照順序執行,也不一(yī)定會在DOMContentLoaded事件觸發前執行,因此最好隻包含一(yī)個延遲腳本。
IE4~IE7還支持對嵌入腳本的(de)defer屬性,但IE8以及之後的(de)版本則完全支持HTML5規定的(de)行為(wèi)。IE4,Firefox 3.5,Safari 5和(hé)Chrome是最早支持defer屬性的(de)浏覽器。其他浏覽器忽略這個屬性,像平常一(yī)樣處理(lǐ)腳本。為(wèi)此,把延遲腳本放在頁面底部仍然是最佳選擇。
defer 和(hé) async 在網絡讀取(下載)這塊兒是一(yī)樣的(de),都是異步的(de)(相較于 HTML 解析)它倆的(de)差别在于腳本下載完之後何時執行,顯然 defer 是最接近我們對于應用腳本加載和(hé)執行的(de)要求的(de)
async 則是一(yī)個亂序執行的(de)主,對它來說腳本的(de)加載和(hé)執行是緊緊挨着的(de),所以不管你聲明的(de)順序如(rú)何,隻要它加載完了就會立刻執行,async 對于應用腳本的(de)用處不大,因為(wèi)它完全不考慮依賴(哪怕是最低(dī)級的(de)順序執行),不過它對于那些可(kě)以不依賴任何腳本或不被任何腳本依賴的(de)腳本來說卻是非常合适的(de),最典型的(de)例子(zǐ):Google Analytics。
原文鏈接:https://blog.csdn.net/ssisse/article/details/51698307
編輯:--ns868