前言
我們知道一個頁面通常由,html,css,js三部分組成,一般我們會把css檔案放在head頭部載入,而js檔案則放在頁面的最底部載入,想要知道為什麼大家都會不約而同的按照這個標準進行構建頁面,必須先得了解頁面的載入過程。(當然以現在的技術你也可以不按這個標準,下面會有講到js的非同步載入問題)
之前寫過一篇超詳細講解頁面載入過程,這裡會比較詳細的介紹從輸入URL到展現一個頁面的詳細過程,今天我們主要來看一下頁面的構建過程,以及html,css,js三者之間的關係。
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖
第一時間獲取最新文章~
頁面構建過程
這裡我們主要考慮三種情況下的構建過程
頁面中只含有外部CSS檔案
從圖中我們可以看到,在構建佈局樹之前需要先獲取到DOM樹與CSSOM樹,在請求回HTML檔案時,HTML解析器響應HTML資料開始構建DOM,但是由於此時頁面中包含的是外部CSS檔案,所以他需要先去請求CSS會見,再獲取到CSS資料後CSS解析器才能開始構建CSSOM。所以這種情況下CSS沒有阻塞DOM的構建,但它阻塞了頁面的渲染
頁面中包含內聯JS和外部CSS
從這張圖中我們可以看到,HTML解析器在構建DOM過程中如果遇到了JS就會停止構建DOM,先去解析執行JS(因為JS可能會修改DOM)。但是在執行JS指令碼之前,如果頁面中包含外部CSS或內聯CSS,會先將CSS構建成CSSOM,再去執行JS。這也就是上面說到的為什麼一般將JS檔案放在頁面的最底部的原因。
所以從這種情況來看,CSS、JS會阻塞DOM的構建,CSS會阻塞JS的執行,但它們不會阻塞HTML的解析
頁面中包含外部JS與外部CSS
從這張圖我們可以看到,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>
(預設指令碼)
從上圖可知,HTML解析器在解析過程中如果遇到script
標籤?️,就會暫停解析,先去請求下載script
指令碼,下載完接著執行該指令碼程式碼,執行完之後再繼續解析HTML。
所以script 阻塞了瀏覽器對 HTML 的解析,如果獲取 JS 指令碼的網路請求遲遲得不到響應,或者 JS 指令碼執行時間過長,都會導致白屏,使用者看不到頁面內容。
<script async>
(非同步指令碼)
從上圖可知,HTML解析器在解析過程如果遇到script async
標籤,該指令碼的請求下載是非同步的,不會阻塞HTML的解析,但是如果指令碼下載回來時,HTML還沒有解析完成,這時候會暫停HTML的解析,先去執行指令碼內容,執行完成後,再繼續解析HTML。
當然它還有一種情況就是當非同步指令碼下載回來時,HTML解析已經完成了,那該指令碼就對HTML沒啥影響,下載完直接執行就好了。
所以該方法的執行是不可控的,因為無法確定指令碼的下載速度與指令碼內容的執行速度,如果存在多個script async
時,他們之間的執行的順序也是不可控的,完全取決於各自的下載速度,誰先下載完成就先執行誰。
<script defer>
(非同步延遲指令碼)
從上圖可知,HTML解析器在解析過程如果遇到script defer
標籤,該指令碼的請求下載也是非同步的,不會阻塞HTML的解析,並且在指令碼下載完之後如果HTML還沒解析完成,該指令碼也不會阻塞HTML解析,而是會等HTML解析完成之後再執行。
如果存在多個script defer
標籤時,他們之間的執行順序會按他們在HTML文件中的順序來進行,這樣能夠保證JS指令碼之間的依賴關係。
總結
- 如果指令碼是模組化的,並且指令碼之間沒有依賴關係,使用
async
- 如果指令碼之間有依賴關係,使用
defer
- 如果指令碼內容比較小,並且被一個非同步指令碼依賴,使用
預設指令碼
(不放任何屬性)
指令碼型別 | 是否阻塞HTML的解析 | 指令碼的執行順序 |
---|---|---|
<script> |
是 | 與在HTML文件中的順序一致 |
<script async> |
可能阻塞也可能不阻塞 | 與網路請求回指令碼檔案的順序一致 |
<script defer> |
否 | 與在HTML文件中的順序一致 |
推薦閱讀
- HTTP發展史,HTTP1.1與HTTP2.0的區別
- 超全面總結Vue面試知識點,助力金三銀四
- 【面試必備】前端常見的排序演算法
- CSS效能優化的幾個技巧
- 前端常見的安全問題及防範措施
- 為什麼大廠前端監控都在用GIF做埋點?
- 前端人員不要只知道KFC,你應該瞭解 BFC、IFC、GFC 和 FFC
我是南玖,我們下期見!!!