現代瀏覽器效能優化-JS篇

GeoffZhu虛筆發表於2017-10-16

現代瀏覽器效能優化-JS篇

眾所周知,JS的載入和執行會阻塞瀏覽器渲染,所以目前業界普遍推薦把script放到body結束之前,以解決js執行時找不到dom等問題。但隨著現代瀏覽器的普及,瀏覽器為我們提供了更多強大的武器,合理利用,方可大幅提高頁面載入速度。

理解渲染過程(HTML Parser)

首先我們從瀏覽器的角度解釋一下從輸入URL到頁面展示經歷了些什麼,以如下html文件舉例

<html>
<head>
    <link rel="stylesheet" type="text/css" href="/style.css">
    <script type="text/javascript" src="/header.js"></script>
</head>
<body>
  <p>Text</p>
  <script type="text/javascript" src="/main.js"></script>
</body>
</html>複製程式碼

瀏覽器自上而下讀取html文件(此過程叫html parser),當發現style.css檔案時,瀏覽器parser停下來去搞css,等style.css下載並解析完畢,瀏覽器繼續parser。緊接著發現header.js, 於是html parser又停了,瀏覽器下載並執行完header.js,繼續parser。此時螢幕上還什麼都沒有。...parser,發現p標籤,遂將p中文字展示了出來。緊接著又發現main.js,瀏覽器又停下parser,下載並執行完main.js才繼續parser,直到頁面渲染完畢。

我們假設header.js中只有一行程式碼console.log('header'), 但伺服器響應很慢,要10秒才能把它返回給瀏覽器,瀏覽器執行這段程式碼需要1ms,那在這 10s+1ms 內,頁面將一直空白。瀏覽器執行JS的時間取決於程式碼質量和硬體,並不是前端工程師隨便可以優化的,所以優化的重點在JS的下載時間。

優化核心:減少JS下載時間

預先解析DNS

非常簡單,效果立竿見影,加快頁面載入時間,多用於預解析CDN的地址的DNS

<!--在head標籤中,越早越好-->
<link rel="dns-prefetch" href="//example.com">複製程式碼

Preload

瀏覽器會在遇到如下link標籤時,立刻開始下載main.js(不阻塞parser),並放在記憶體中,但不會執行其中的JS語句。
只有當遇到script標籤載入的也是main.js的時候,瀏覽器才會直接將預先載入的JS執行掉。

<link rel="preload" href="/main.js" as="script">複製程式碼

Prefetch

瀏覽器會在空閒的時候,下載main.js, 並快取到disk。當有頁面使用的時候,直接從disk快取中讀取。其實就是把決定是否和什麼時間載入這個資源的決定權交給瀏覽器。

如果prefetch還沒下載完之前,瀏覽器發現script標籤也引用了同樣的資源,瀏覽器會再次發起請求,這樣會嚴重影響效能的,載入了兩次,,所以不要在當前頁面馬上就要用的資源上用prefetch,要用preload。

<link href="main.js" rel="prefetch">複製程式碼

JS在什麼時候執行的(defer和async)

上面我們的例子中,script標籤都是在沒有多餘屬性的情況下執行的,只要下載過程結束,瀏覽器就會將JS執行掉。
defer和async是script標籤的兩個屬性,用於在不阻塞頁面文件解析的前提下,控制指令碼的下載和執行。

defer,async與下載時機也有關,具體看這張圖。

defer的執行時間是在所有元素解析完成之後,DOMContentLoaded 事件觸發之前。

async的執行時間是在當前JS指令碼下載完成後,所以多個async script是執行順序是不固定的。所以async只能用於載入一些獨立無依賴的程式碼,比如Google Analysis之類。

完美的結構

前面兩節幫我們弄懂了JS的下載和執行時機,那什麼樣的頁面才是完美符合現代瀏覽器的那?其實關鍵在於的preload和prefetch!提前告知瀏覽器,我們的網站馬上要用的是什麼,以後可能要用的是什麼,瀏覽器才能更快的渲染頁面。下面是一段例項程式碼

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Faster</title>
  <link rel="dns-prefetch" href="//cdn.com/">
  <link rel="preload" href="//js.cdn.com/currentPage-part1.js" as="script">
  <link rel="preload" href="//js.cdn.com/currentPage-part2.js" as="script">
  <link rel="preload" href="//js.cdn.com/currentPage-part3.js" as="script">

  <link rel="prefetch" href="//js.cdn.com/prefetch.js">
</head>
<body>

<script type="text/javascript" src="//js.cdn.com/currentPage-part1.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part2.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part3.js" defer></script>
</body>
</html>複製程式碼

首先,Parser在遇到head中preload時開始下載JS,讀到script標籤的時候,如果已經下載完了,直接按順序執行之。如果沒下載完,會等到下載完再執行,這樣就剛進入頁面就開始非阻塞的下載JS了。

其次,頁面會在空閒時,載入prefetch的JS,如果之後頁面發生跳轉,跳轉的目標頁面引入了prefetch.js,瀏覽器會直接從disk快取中讀取執行。

將script標籤依然放在body結束之前,並增加defer標籤,確保老瀏覽器相容,並在所有DOM元素解析完成之後執行其中的程式碼。

至此,完美的HTML結構出爐了。

CSS的下載和解析一樣會阻塞瀏覽器,造成白屏,CSS中的字型檔案更是影響首屏渲染關鍵因素之一,下一篇幅我會結合preload和prefetch,帶你一起優化CSS,告訴你什麼是最適合現代瀏覽器的CSS載入策略,期待的話,點個贊吧!

相關文章