現流行瀏覽器對於靜態資源的預加載
傳統的瀏覽器,對於靜態資源加載,會阻塞 HTML 解析器的線程進行,無論內聯還是外鏈。
例如:
<script src="test1.js"></script>
<script src="test2.js"></script>
<script src="test3.js"></script>
<img src="img.png" />
傳統瀏覽器HTML解析器,會從test1.js 逐一解析到img.png,只個解析過程是同步的,只有當test1.js解析加載完成才會到test2.js 順序加載。假設js文件加載時間需要1秒,img文件也需要1秒的時間,那么除去頁面其他階段的render時間不計,img圖片會是4秒之后才顯示給用戶。
然而,現在主流瀏覽器對於靜態資源的解析,已經做到了預解析和預加載。
相比傳統瀏覽器,當瀏覽器HTML解析器,遇到test1.js靜態資源的是,主線程中的解析器暫停解析,瀏覽器會新開啟一個解析器線程,去預加載后面的資源。假設js文件加載時間需要1秒,img文件也需要1秒的時間。當瀏覽器遇到第一個js文件test1.js的時候,會新開啟一個解析器線程,去預加載解析其他靜態資源。除去頁面其他階段的render時間不計,那么img圖片只會是2秒之后才顯示給用戶。
但瀏覽器能做的僅僅是預解析和預加載,腳本的執行和 DOM 樹的構建仍然必須是線性的
document.write() 打破瀏覽器的預解析和預加載,因為document.write()加載的JS文件無法讓HTML預解析器發現
<script src="test1.js"></script>
<script>
document.write('<script src="http://xxx.com/test2.js"><\/script>')
</script>
<script src="test3.js"></script>
這個例子中,由於 test2.js 是通過 JS 代碼插入的,HTML 預解析器是看不到的,所以只有當 test1.js 下載並執行完畢,且第二個內聯的 script 執行完畢后,test2.js 才會開始下載,也就是說,test2.js 不能和 test1.js 及 test3.js 並行下載了,從而導致頁面展現變慢,同樣假設每個文件的下載時間都是 1 秒,那么這三個文件下載執行完就需要兩秒,就因為 test2.js 不能預加載。在一個外鏈的 JS 文件比如 a.js 中執行 document.write("<script...) 也是類似的效果。
針對document.write() 去加載靜態資源,我們可以做出什么優化?
將資源轉成外鏈的方式加載。
如下
<script src="test1.js"></script>
<script>
document.write('<script src="http://xxx.com/test2.js"><\/script>')
</script>
<script src="test3.js"></script>
改成
<script src="test1.js"></script>
<script src="test2.js"></script>
<script src="test3.js"></script>
盡可能,讓瀏覽器預解析器發現靜態資源文件。但是這並不意味着頁面的加載時間會大大減少。
假設test1.js的加載時間為1秒,test2.js的加載時間為10秒。即便test1.js之后的靜態資源參與了瀏覽器的預解析加載,為了配合這句話“但瀏覽器能做的僅僅是預解析和預加載,腳本的執行和 DOM 樹的構建仍然必須是線性的
”。頁面始終會全部資源加載完之后才完成渲染,test2.js的10秒加載時間仍舊會讓頁面處於loading轉圈狀態。
對於非第三方的靜態資源的加載時間太長,應考慮前端資源的優化,這里列出來可能多的優化方案,但是暫不做詳解
- 減少靜態文件的文件大小 (代碼壓縮)
- 減少靜態文件請求數量 (文件合並)
- gzip
- CDN和緩存
對於第三方的靜態資源文件,可以使用async實現異步加載
async 加載靜態資源
一句話概括便是異步的 script 根本不會阻塞 HTML 解析器,也就用不到預解析了
目前大部分第三方庫都會支持async 異步 加載JS資源,然后去調用一個全局function 例如
<script src="test.js?callback=dosomething" async ></script>
async 的js資源,如果長時間pedding 會影響onload加載時間
關於document.write()的其他知識點
ducument.write 在onload之前,插入執行document.write()都會給頁面插入內容,頁面onload完成之后,瀏覽器輸出流自動關閉;在此之后,任何一個對當前頁面進行操作的document.write()方法將打開—個新的輸出流
如下
結果是:
頁面onload完成之后,調用document.write
結果是: