1. 程式人生 > >JS的阻塞載入和 defer 和 async 屬性

JS的阻塞載入和 defer 和 async 屬性

JS具有阻塞特性,當瀏覽器在執行js程式碼時,不能同時做其它事情,即<script>每次出現都會讓頁面等待指令碼的解析和執行(不論JS是內嵌的還是外鏈的),JS程式碼執行完成後,才繼續渲染頁面。

所有瀏覽器在下載JS的時候,會阻止一切其他活動,比如其他資源的下載,內容的呈現等等。至到JS下載、解析、執行完畢後才開始繼續並行下載其他資源並呈現內容。

有人會問:為什麼JS不能像CSS、image一樣並行下載了?這裡需要簡單介紹一下瀏覽器構造頁面的原理,
當瀏覽器從伺服器接收到了HTML文件,並把HTML在記憶體中轉換成DOM樹,在轉換的過程中如果發現某個節點(node)上引用了CSS或者IMAGE,就會再發1個request去請求CSS或image,然後繼續執行下面的轉換,而不需要等待request的返回,當request返回後,只需要把返回的內容放入到DOM樹中對應的位置就OK。但當引用了JS的時候,瀏覽器傳送1個js request就會一直等待該request的返回。因為瀏覽器需要1個穩定的DOM樹結構,而JS中很有可能有程式碼直接改變了DOM樹結構,比如使用document.write 或 appendChild,甚至是直接使用的location.href進行跳轉,瀏覽器為了防止出現JS修改DOM樹,需要重新構建DOM樹的情況,所以就會阻塞其他的下載和呈現.

阻塞下載圖:下圖是訪問blogjava首頁的時間瀑布圖,可以看出來開始的2個image都是並行下載的,而後面的2個JS都是阻塞下載的(1個1個下載)。


由於,JS的這種阻塞特性,每次遇到<script>,頁面都必須停下來等待指令碼下載並執行,這會停止頁面繪製,帶來不好的使用者體驗。所以,有必要減少JS阻塞特性造成的困擾。

1 優化指令碼位置

HTML4規範中,<script>可以放在<head>或<body>中。你可能習慣性的在<head>中放置多個外鏈JS、CSS,以求優先載入它們。瀏覽器在繼續到<body>之前,不會渲染頁面,所以,把JS放在<head>中,會導致延遲。為了提高使用者體驗,新一代瀏覽器都支援並行下載JS,但是JS下載仍然會阻塞其它資源的下載(eg.圖片)。儘管指令碼的下載過程並不會相互影響,但頁面仍然必須等待所有JS下載並執行完成才能繼續。顯見,所有<script>應該儘可能放到<body>的底部,以減少對頁面下載的影響。

注意:CSS檔案本身是並行下載,不會阻塞頁面的其他程序。但是,如果把一段內嵌指令碼放在引用外鏈CSS的<link>之後會導致頁面阻塞去等待CSS的下載。這樣做是為了確保內嵌指令碼在執行時能夠獲得正確的樣式資訊。所以,最好不要把內嵌指令碼放在CSS的<link>之後。

2 減少外鏈指令碼數量以改善效能

 原因很簡單,額外的HTTP請求會帶來額外的開銷,所以減少頁面中外鏈指令碼的數量,有助於改善效能。

3 使用無阻塞下載JS方法

無阻塞指令碼的祕訣在於,在頁面載入完成後才載入JS,即在window物件的load事件觸發後在下載指令碼。

4 使用<script>的defer屬性(僅IE和Firefox3.5以上);

defer屬性指明本元素所含的指令碼不會修改DOM,因此程式碼能安全的延遲執行。defer屬性的<script>,對應的JS檔案將在頁面解析到<script>時開始下載,但並不會執行,直到DOM載入完成,即onload事件觸發前被呼叫。當一個帶有defer屬性的JS檔案下載時,他不會阻塞瀏覽器的其它程序,因此這類檔案可以與頁面中的其他資源並行下載。

5 嵌入JS應該放在什麼位置


   1、放在底部,雖然放在底部照樣會阻塞所有呈現,但不會阻塞資源下載。
   
   2、如果嵌入JS放在head中,請把嵌入JS放在CSS頭部。
   
   3、使用defer
   
   4、不要在嵌入的JS中呼叫執行時間較長的函式,如果一定要用,可以用setTimeout來呼叫
   

PS:很多網站喜歡在head中嵌入JS,並且習慣放在CSS後面,比如看到的www.qq.com,當然也有很多網站是把JS放到CSS前面的,比如yahoo,google

js的[defer]和[async]屬性

HTML4.01為<script>定義了6個屬性,包括defer和async。defer和async都是可選的,且只對外部指令碼檔案有效。

一、當瀏覽器解析到script指令碼,沒有defer或async時:

<script src="main.js"></script>

瀏覽器會立即載入並執行指定的指令碼,“立即”指在渲染該script標籤之下的文件元素之前,也就是說不等待後續載入的文件元素,讀到就載入並執行。

二、當瀏覽器解析到script指令碼,有async時:

<script async src="main.js"></script>

瀏覽器會立即下載指令碼,但不妨礙頁面中的其他操作,比如下載其他資源或等待載入其他指令碼。載入和渲染後續文件元素的過程和main.js的載入與執行並行進行(非同步)。

async不保證按照指令碼出現的先後順序執行,因此,確保兩者之前互不依賴非常重要,指定async屬性的目的是不讓頁面等待兩個指令碼的下載和執行,從而非同步載入頁面其他內容,建議非同步指令碼不要在載入期間修改DOM。

非同步指令碼一定會在頁面的load事件前執行,但可能會在DOMContentLoaded事件觸發之前或之後執行。支援非同步指令碼的瀏覽器有Firefox 3.6、Safari 5 和Chrome。

三、當瀏覽器解析到script指令碼,有defer時:

<script defer="defer" src="main1.js"></script>
<script defer="defer"  src="main2.js"></script>

表示指令碼會被延遲到文件完全被解析和顯示之後再執行,載入後續文件元素的過程將和main.js的載入並行進行(非同步)。

HTML5規範要求指令碼按照它們出現的先後順序執行,因此第一個延遲指令碼會先於第二個延遲指令碼執行,而這兩個指令碼會先於DOMContentLoaded事件。

在現實當中,延遲指令碼並不一定會按照順序執行,也不一定會在DOMContentLoaded事件觸發前執行,因此最好只包含一個延遲指令碼。

IE4~IE7還支援對嵌入指令碼的defer屬性,但IE8以及之後的版本則完全支援HTML5規定的行為。IE4,Firefox 3.5,Safari 5和Chrome是最早支援defer屬性的瀏覽器。其他瀏覽器忽略這個屬性,像平常一樣處理指令碼。為此,把延遲指令碼放在頁面底部仍然是最佳選擇。

defer 和 async 在網路讀取(下載)這塊兒是一樣的,都是非同步的(相較於 HTML 解析)它倆的差別在於指令碼下載完之後何時執行,顯然 defer 是最接近我們對於應用指令碼載入和執行的要求的

async 則是一個亂序執行的主,對它來說指令碼的載入和執行是緊緊挨著的,所以不管你宣告的順序如何,只要它載入完了就會立刻執行,async 對於應用指令碼的用處不大,因為它完全不考慮依賴(哪怕是最低階的順序執行),不過它對於那些可以不依賴任何指令碼或不被任何指令碼依賴的指令碼來說卻是非常合適的,最典型的例子:Google Analytics。

參考: