前言
我們知道一個頁面通常由,html,css,js三部分組成,一般我們會把css檔案放在head頭部加載,而js檔案則放在頁面的最底部加載,想要知道為什麼大家都會不約而同的按照這個标準進行建構頁面,必須先得了解頁面的加載過程。(當然以現在的技術你也可以不按這個标準,下面會有講到js的異步加載問題)
之前寫過一篇超詳細講解頁面加載過程,這裡會比較詳細的介紹從輸入URL到展現一個頁面的詳細過程,今天我們主要來看一下頁面的建構過程,以及html,css,js三者之間的關系。
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公衆号首發,關注
前端南玖
第一時間擷取最新文章~
頁面建構過程
這裡我們主要考慮三種情況下的建構過程
頁面中隻含有外部CSS檔案
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsAjMfd3bkFGazxCMx8VesATMfhHLlN3XnxCMz8FdsYkRGZkRG9lcvx2bjxSa2EWNhJTW1AlUxEFeVRUUfRHelRHL2EzXlpXazxyayFWbyVGdhd3LcV2Zh1Wa9M3clN2byBXLzN3btg3PwJWZ35SM2YTO0QWM1kTOjZDOlNTNyYzX2UTNzYTMxEzLcRDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.webp)
從圖中我們可以看到,在建構布局樹之前需要先擷取到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>
(預設腳本)
<script>
從上圖可知,HTML解析器在解析過程中如果遇到
script
标簽????️,就會暫停解析,先去請求下載下傳
script
腳本,下載下傳完接着執行該腳本代碼,執行完之後再繼續解析HTML。
是以script 阻塞了浏覽器對 HTML 的解析,如果擷取 JS 腳本的網絡請求遲遲得不到響應,或者 JS 腳本執行時間過長,都會導緻白屏,使用者看不到頁面内容。
<script async>
(異步腳本)
<script async>
從上圖可知,HTML解析器在解析過程如果遇到
script async
标簽,該腳本的請求下載下傳是異步的,不會阻塞HTML的解析,但是如果腳本下載下傳回來時,HTML還沒有解析完成,這時候會暫停HTML的解析,先去執行腳本内容,執行完成後,再繼續解析HTML。
當然它還有一種情況就是當異步腳本下載下傳回來時,HTML解析已經完成了,那該腳本就對HTML沒啥影響,下載下傳完直接執行就好了。
是以該方法的執行是不可控的,因為無法确定腳本的下載下傳速度與腳本内容的執行速度,如果存在多個
script async
時,他們之間的執行的順序也是不可控的,完全取決于各自的下載下傳速度,誰先下載下傳完成就先執行誰。
<script defer>
(異步延遲腳本)
<script defer>
從上圖可知,HTML解析器在解析過程如果遇到
script defer
标簽,該腳本的請求下載下傳也是異步的,不會阻塞HTML的解析,并且在腳本下載下傳完之後如果HTML還沒解析完成,該腳本也不會阻塞HTML解析,而是會等HTML解析完成之後再執行。
如果存在多個
script defer
标簽時,他們之間的執行順序會按他們在HTML文檔中的順序來進行,這樣能夠保證JS腳本之間的依賴關系。
總結
- 如果腳本是子產品化的,并且腳本之間沒有依賴關系,使用
async
- 如果腳本之間有依賴關系,使用
defer
- 如果腳本内容比較小,并且被一個異步腳本依賴,使用
(不放任何屬性)預設腳本
腳本類型 | 是否阻塞HTML的解析 | 腳本的執行順序 |
| 是 | 與在HTML文檔中的順序一緻 |
| 可能阻塞也可能不阻塞 | 與網絡請求回腳本檔案的順序一緻 |
| 否 | 與在HTML文檔中的順序一緻 |