背景
隨著業務功能的增加,Web App的“身軀”變得越來越臃腫,效能優化迫在眉睫,而首當其衝的是首屏渲染速度的問題。
正文
我們常常提及首屏渲染速度,但是鮮有人去給它下一個確切的定義。在我的理解裡面,這裡面的速度並不是指我們狹義裡的速度,而是廣義上的。因為我們無法準確地統計網路傳輸所經過的物理里程,故不能用v=s/t
去計算。那麼如何廣義法呢?其實,我們可以將首屏渲染速度等同為首屏渲染時間來理解。可以這麼說,從使用者在瀏覽器位址列敲入一個url到使用者看到一個完整的頁面,這個過程所花費的時間就是首屏渲染時間。關係到這個首屏渲染時間的長短有很多因素,比如說,DNS解析時間
,網路傳輸時間
和頁面渲染時間
等等。今天,我們就挑其中的一個子環節頁面渲染
來說說。頁面渲染是指從瀏覽器接收到從伺服器返回的HTML檔案那一刻算起,到你眼睛看到一個完整頁面為止的過程。對於前端開發來說,做首屏渲染速度的優化往往會選擇在頁面渲染這個過程發力。通過優化頁面渲染過程,我們可以減少頁面處於白屏狀態的時間,從而達到一定的首屏渲染速度優化效果。
上面只是提到了頁面渲染的一個很表象的說法。其實頁面渲染這個過程包含了一系列的環節,我可以簡單地描述為(如下圖):解析HTML構建DOM樹;解析CSS,構建CSSOM tree;然後將DOM tree和CSSOM tree合併為render tree。再然後使用render tree所提供的資料進行佈局(layout),最後是將整個頁面繪製(paint)到螢幕上。而今天我們的話題跟這其中一個環節-HTML解析
有關。
瀏覽器在解析HTML文件的時候,遇到常規的script標籤會停下來,轉而去載入所請求的指令碼,緊跟著執行載入回來的指令碼。換句話說,使用常規script標籤去請求外部指令碼的話,會阻塞當前的HTML解析。而阻塞當前的HTML解析,就是阻塞整個頁面渲染的過程。所以,我們可以做的就是使得script標籤引用外部指令碼的時候不要阻塞HTML解析。
為了達到這個目的,我們通常會採用以下的方案:
- 把常規的script標籤放置到
</ body>
標籤前。 - 給script標籤新增defer,async標誌位,使它的載入與HTML解析並行進行。
到這裡,我們就引出了我們今天的兩個主角:defer和async。正如上面所說的,新增了這個標誌位的script標籤都會非同步載入指令碼。這是它們共同的特性,那麼它們之間又有什麼不同的地方呢?
它們不同的地方體現在兩個方面:
- 指令碼的執行是不是緊跟著載入發生的?
- 如果文件中有多個設定了相同標誌位的script標籤,它們的載入順序會是如何?
經過查閱資料和親身實踐,我們可以有以下的結論:
對於defer標誌位而言:
- 新增了defer標誌位的script標籤所載入回來的指令碼會在HTML解析完之後,DOMContendLoaded事件發生之前執行。
- 如果文件中有多個設定了defer標誌位的script標籤的話,它們會按照在文件出現的順序來載入和執行的。
對於async標誌位而言:
- 新增了async標誌位的script標籤所載入回來的指令碼會緊接著就執行了。
- 如果文件中有多個設定了async標誌位的script標籤的話,它們不會按照在文件出現的順序來載入和執行的,它們都是亂序的主。
從上面兩個結論來看,使用了defer和async標誌為的script標籤所引用的指令碼雖然在載入階段都不會阻塞HTML解析,但是設定了async標誌位的script標籤還是會在緊接著的執行階段阻塞了HTML解析。並且由於它是亂序的主,所以它不能滿足各個類庫在依賴管理方面的需求。
正所謂一圖勝萬言。常規script標籤,defer標籤和async標籤的載入和執行過程跟HTML解析過程的關係如下圖:
總結
綜上所述,通過調整外部指令碼的載入和執行次序來優化首屏渲染速度諸多方案中,為script標籤新增defer標誌位無疑是最優的。