利用Resource Timing監控資源加載速度

Resource Timing API

Resource Timing API提供了讓用戶查看一個資源從輸入url到下載下來經歷的各個過程所消耗的時間,藉此能夠來衡量網站的性能。 咱們能夠經過Resource Timing Api監控哪一個階段消耗時間比較長,而後針對該階段進行優化,好比發現一個請求的過程當中服務器返回時間過長,則須要對服務器進行優化了。css

資源請求的生命週期

瀏覽器從請求資源到資源下載下來,會通過多個階段,一個請求生命週期的主要階段包括:html

  1. 重定向;若是服務器返回302的狀態時,則會發生重定向,頁面會重定向到302響應的location屬性指定的地址去(response的Location屬性指定,例如jd.com會被跳轉到http://www.jd.com)。
  2. 讀取瀏覽器緩存;若是資源的緩存時間還未過時(服務器設置的expires和cache-control還未過時),則會直接從瀏覽器緩存中讀取。後續的dns查詢、tcp握手、請求的發送都不會進行了。
  3. DNS解析;發送http請求須要經過創建TCP鏈接,創建TCP鏈接須要直到目標機器(服務器)的ip地址,因此須要進行dns的解析出ip,而後經過socket創建tcp鏈接。
  4. TCP握手;http創建在TCP上,須要先完成TCP三次握手,經過第2步的ip和已知的端口號,瀏覽器開啓一個進程創建TCP鏈接。在此過程當中,若是使用https,則會在TCP鏈接的時候進行SSL握手,創建SSL鏈接。
  5. 請求request;瀏覽器根據url,組裝http請求,併發送。
  6. 接收response;服務器返回http響應的時候,瀏覽器接收到http response,而後就下載資源了。

圖1

查看各個階段資源加載時間

devtools

咱們能夠藉助chrome的devtools查看,在network選項卡下,點擊某個請求,選中Timing就能夠看到devtools給咱們提供的關於timing resource各個階段的詳細時間了。 web

圖1

圖2

Queueing: 請求被阻塞,放入等待隊列中等待。
通常如下幾個緣由會被阻塞:chrome

一、若是這個資源加載優先級比較低,好比圖片(html/css/js的優先級比圖片高),那麼圖片請求就會被渲染引擎阻塞,等待優先級高的資源加載完成才從隊列中取出,等待發送。
二、咱們知道瀏覽器對同一域名下對TCP鏈接的併發數有限制(防止資源被消耗殆盡),chrome這邊是6,那麼若是同一域名下請求多於6的話,後面的請求就會被阻塞。
三、等待釋放TCP鏈接瀏覽器

Stalled: 等待發送所用的時間,緣由同上。緩存

DNS Lookup:DNS查詢時間bash

Initail connection:創建TCP鏈接所用的時間服務器

SSL:創建SSL鏈接所用的時間網絡

Request sent:發出請求的時間,一般不到一毫秒併發

TTFB:第一字節時間,即請求發出到接受到服務器第一個字節的時間,若是這個時間太長,通常有兩個緣由:

一、網絡太差
二、服務器響應太慢

通常建議不要這個階段的時間不要超過200毫秒。

Content Download:資源下載時間,若是被阻塞,則這個時間會很長,或者資源過大也會致使下載時間過長。例如js執行時間過長,那麼圖片加載下來的時間就會被拉到很長。

Resource Timing Api

現代瀏覽器提供了Api讓用戶能夠查看圖1各個階段所消耗的時間,以便用戶用Api獲取資源加載過程當中的具體狀況,排查耗時的階段,而後進行對應的優化。

經過window.performance.getEntriesByType('resource')獲取全部的PerformanceResourceTiming:

if('performance' in window) {
    // 獲取的是全部的PerformanceResourceTiming
    var resources = window.performance.getEntriesByType('resource')
    // 遍歷各個資源加載的時間
    resources.map((resource) => {
        // 這裏以圖片爲例,判斷圖片加載的時間
        if(resource.initiatorType === 'img') {
            // duration取的是整個過程當中經歷的時間,即圖1的startTime到responseEnd直接的時間,即等於resource.responseEnd - resource.startTime
            if(resource.duration > 5000) {
                // 圖片加載超過了5秒了,上報服務器,提示圖片加載過長
                reportToServer()
            }
        }
    })
}
複製代碼

注意,上面的代碼須要在onload事件上面執行(onload會在圖片加載完畢之後調用)。

PerformanceResourceTimeing包含如下的屬性:

  • [x] initiatorType:資源的類型,有img、script、link

下面的屬性是以毫秒爲單位,對應圖1

  • [x] redirectStart
  • [x] redirectEnd
  • [x] fetchStart
  • [x] domainLookupStart
  • [x] domainLookupEnd
  • [x] connectStart
  • [x] connectEnd
  • [x] secureConnectionStart
  • [x] requestStart
  • [x] responseStart
  • [x] responseEnd

因此咱們得出這樣的一個計算:
查看DNS查詢時間: domainLookupEnd - domainLookupStart
查看TCP三次握手時間: connectEnd - connectStart
request請求時間: responseEnd - responseStart
整個過程時間: responseEnd - startTime 或者 duration

資源加載

瀏覽器渲染過程(關鍵渲染路徑)

一、根據HTML構建DOM樹
二、根據css構建CSSOM規則樹
三、根據DOM樹和CSSOM樹渲染合併渲染樹
四、根據渲染樹計算元素的位置和大小
五、將元素顯示在屏幕上

在此過程當中,解析到script會阻塞dom的解析和渲染(但其餘資源的下載仍是並行下載的)。執行完js後又會從新構建DOM樹和CSSOM樹,再構建渲染樹,如此反覆。

css加載

css被視爲阻塞渲染的資源,不會阻塞dom的解析,但會阻塞dom的渲染。瀏覽器爲了不dom渲染完後,css樣加載完再去渲染一次,就會阻塞dom的渲染。否則會有兩次渲染,從性能上和用戶體驗上都很差。
若是把加載css放在body後,瀏覽器爲了防止上面的情況的發生,會等待css加載完纔去渲染dom,這樣就會白屏了,因此建議css放在head裏去加載。
因此,css放在head加載是一個很好的優化。

js加載

js即會阻塞dom的解析,也會阻塞dom的渲染。js是單線程的,在執行js的時候,瀏覽器會將控制權交給js,dom解析和渲染都會阻塞。
若是把js加載放在頭部,那麼dom的解析和渲染就中止了,這樣會致使兩個問題:
一、一方面,若是這時候js要獲取dom或者操做dom都會報錯。
二、另外一方面,用戶等待頁面展現出來的時間也會加長,因此建議js加載放在底部。

備註:若是都放在head中,css在前,js在後,則瀏覽器爲了讓js獲取到的樣式是準確的,則會在css加載完前阻塞js的執行。若是把js寫在前,css在後,瀏覽器會預加載css,這樣的效果會比css在前阻塞後面的js執行好。

咱們能夠將script的加載設爲異步加載,即defer/async,這樣它就不會阻塞dom的解析和渲染。

結合以上,咱們建議把css的引入放在head,把js的引入放在body以後,若是js能夠異步加載,咱們可使用異步加載的方式。多說一句,現代瀏覽器有freload和frefetch的預加載,也能夠提升頁面加載速度,有興趣的能夠去查閱下資料。

圖片加載

圖片的加載不會阻塞dom的渲染,試想下,若是圖片加載會阻塞dom的渲染的話,那麼在多圖的網站(如今很常見),等待圖片所有加載出來纔去渲染dom,那麼用戶體驗將會是特別的差,因此圖片的加載順序優先級是比較低的。

參考連接:
developers.google.com/web/tools/c…
developers.google.com/web/fundame… juejin.im/post/59c606…
juejin.im/entry/59e1d…

相關文章
相關標籤/搜索