性能優化之html、css、js三者的加載順序

前言

我們知道一個頁面通常由,html,css,js三部分組成,一般我們會把css文件放在head頭部加載,而js文件則放在頁面的最底部加載,想要知道為什麼大家都會不約而同的按照這個標準進行構建頁面,必須先得了解頁面的加載過程。(當然以現在的技術你也可以不按這個標準,下面會有講到js的異步加載問題)

之前寫過一篇超詳細講解頁面加載過程,這裡會比較詳細的介紹從輸入URL到展現一個頁面的詳細過程,今天我們主要來看一下頁面的構建過程,以及html,css,js三者之間的關係。

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新文章~

頁面構建過程

這裡我們主要考慮三種情況下的構建過程

頁面中只含有外部CSS文件

1.jpeg

從圖中我們可以看到,在構建布局樹之前需要先獲取到DOM樹與CSSOM樹,在請求回HTML文件時,HTML解析器響應HTML數據開始構建DOM,但是由於此時頁面中包含的是外部CSS文件,所以他需要先去請求CSS會見,再獲取到CSS數據後CSS解析器才能開始構建CSSOM。所以這種情況下CSS沒有阻塞DOM的構建,但它阻塞了頁面的渲染

頁面中包含內聯JS和外部CSS

2.png

從這張圖中我們可以看到,HTML解析器在構建DOM過程中如果遇到了JS就會停止構建DOM,先去解析執行JS(因為JS可能會修改DOM)。但是在執行JS腳本之前,如果頁面中包含外部CSS或內聯CSS,會先將CSS構建成CSSOM,再去執行JS。這也就是上面說到的為什麼一般將JS文件放在頁面的最底部的原因。

所以從這種情況來看,CSS、JS會阻塞DOM的構建,CSS會阻塞JS的執行,但它們不會阻塞HTML的解析

頁面中包含外部JS與外部CSS

3.png

從這張圖我們可以看到,HTML解析器在解析過程中如果遇到外部CSS與外部JS文件,就會同時發起請求對文件進行下載,這個過程DOM構建的過程會停止,需要等CSS文件下載完成並構建完CSSOM,JS文件下載完成並執行結束,才會開始構建DOM。我們知道CSS會阻塞JS的執行,所以JS必須要等到CSSOM構建完成之後再執行

所以上面我們說的CSS放在頭部進行加載,而JS文件放在頁面的底部進行加載也就能夠解釋的通了。

CSS與DOM的關係

CSS不會阻塞DOM的解析,但會阻塞DOM的渲染

CSSOM的作用

  • 第一個是提供給JavaScript操作樣式表的能力
  • 第二個是為布局樹的合成提供基礎的樣式信息
  • 這個CSSOM體現在DOM中就是document.styleSheets

由之前講到的瀏覽器渲染流程我們可以看出:

  • DOM和CSSOM通常是並行構建的,所以CSS加載不會阻塞DOM的解析
  • render樹是依賴DOM樹和CSSOM樹的,所以它必須等到兩者都加載完畢才能開始構建渲染,所以CSS加載會阻塞DOM的渲染

CSS與JS的關係

CSS會阻塞JS執行,但不會阻塞JS文件的下載

由於JavaScript是可以操作DOM與CSS的,如果在修改這些元素屬性同時渲染界面(即JavaScript線程與UI線程同時進行),那麼渲染線程前後獲得的元素可能就不一致了。所以為了防止渲染出現不可預期的結果,瀏覽器設置GUI渲染線程與JavaScript線程為互斥的關係

如果JS腳本的內容是獲取元素的樣式,那它就必然依賴CSS。因為瀏覽器無法感知JS內部到底想幹什麼,為避免樣式獲取,就只好等前面所有的樣式下載完畢再執行JS。但JS文件與CSS文件下載是並行的,CSS文件會在後面的JS文件執行前先加載執行完畢,所以CSS會阻塞後面JS的執行

JS與DOM的關係

JS會阻塞DOM的解析,因此也就會阻塞頁面的加載

這也是為什麼我們常說要把JS文件放在最下面的原因

由於 JavaScript 是可操縱 DOM 的,如果在修改這些元素屬性同時渲染界面(即 JavaScript 線程和 UI 線程同時運行),那麼渲染線程前後獲得的元素數據就可能不一致了。

因此為了防止渲染出現不可預期的結果,瀏覽器設置 GUI 渲染線程與 JavaScript 引擎為互斥的關係

當 JavaScript 引擎執行時 GUI 線程會被掛起,GUI 更新會被保存在一個隊列中等到引擎線程空閑時立即被執行。

當瀏覽器在執行 JavaScript 程序的時候,GUI 渲染線程會被保存在一個隊列中,直到 JS 程序執行完成,才會接着執行。

因此如果 JS 執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞的感覺。

結論

  • CSS不會阻塞DOM的解析,但會阻塞DOM的渲染
  • CSS會阻塞JS的執行,但不會阻塞JS的下載
  • JS會阻塞DOM的解析,也就會阻塞DOM的渲染

由於CSS與JS都會阻塞DOM的渲染,我們應該儘可能的提高CSS的加載速度,將JS延遲加載。

優化CSS加載

  • 使用CDN(CDN會根據用戶網絡狀況挑選最近的一個具有緩存內容的節點,可以減少加載時間)

  • 壓縮CSS文件(可以用很多打包工具,比如webpack,gulp等,也可以通過開啟gzip壓縮)

  • 合理使用緩存(設置cache-control,expires,以及E-tag都是不錯的,不過要注意一個問題,就是文件更新後,你要避免緩存而帶來的影響。其中一個解決防範是在文件名字後面加一個版本號)

  • 減少http請求數,將多個css文件合併,或者是乾脆直接寫成內聯樣式(內聯樣式的一個缺點就是不能緩存)

CSS優化部分推薦閱讀CSS性能優化的幾個技巧

JS延遲加載

在HTML中加載外部JS文件的方式一般有三種:

<script src="..."></script>
<script src="..." async></script>
<script src="..." defer></script>

<script>(默認腳本)

4.png

從上圖可知,HTML解析器在解析過程中如果遇到script標籤🏷️,就會暫停解析,先去請求下載script腳本,下載完接着執行該腳本代碼,執行完之後再繼續解析HTML。

所以script 阻塞了瀏覽器對 HTML 的解析,如果獲取 JS 腳本的網絡請求遲遲得不到響應,或者 JS 腳本執行時間過長,都會導致白屏,用戶看不到頁面內容。

<script async>(異步腳本)

5.png

從上圖可知,HTML解析器在解析過程如果遇到script async標籤,該腳本的請求下載是異步的,不會阻塞HTML的解析,但是如果腳本下載回來時,HTML還沒有解析完成,這時候會暫停HTML的解析,先去執行腳本內容,執行完成後,再繼續解析HTML。

當然它還有一種情況就是當異步腳本下載回來時,HTML解析已經完成了,那該腳本就對HTML沒啥影響,下載完直接執行就好了。

所以該方法的執行是不可控的,因為無法確定腳本的下載速度與腳本內容的執行速度,如果存在多個script async時,他們之間的執行的順序也是不可控的,完全取決於各自的下載速度,誰先下載完成就先執行誰。

<script defer>(異步延遲腳本)

6.png

從上圖可知,HTML解析器在解析過程如果遇到script defer標籤,該腳本的請求下載也是異步的,不會阻塞HTML的解析,並且在腳本下載完之後如果HTML還沒解析完成,該腳本也不會阻塞HTML解析,而是會等HTML解析完成之後再執行。

如果存在多個script defer標籤時,他們之間的執行順序會按他們在HTML文檔中的順序來進行,這樣能夠保證JS腳本之間的依賴關係。

總結

  • 如果腳本是模塊化的,並且腳本之間沒有依賴關係,使用async
  • 如果腳本之間有依賴關係,使用defer
  • 如果腳本內容比較小,並且被一個異步腳本依賴,使用默認腳本(不放任何屬性)
腳本類型 是否阻塞HTML的解析 腳本的執行順序
<script> 與在HTML文檔中的順序一致
<script async> 可能阻塞也可能不阻塞 與網絡請求回腳本文件的順序一致
<script defer> 與在HTML文檔中的順序一致

推薦閱讀

原文首發地址點這裡,歡迎大家關注公眾號 「前端南玖」,如果你想進前端交流群一起學習,請點這裡

我是南玖,我們下期見!!!