從一道經典的面試題開始
(本文部分內容整理自網路文章)
使用者從輸入 URL 到頁面載入完成,都發生了什麼?
- DNS 解析 (解析域名,將URL解析為對應的IP地址)
- TCP 連線(與這個IP建立TCP網路連線,三次握手)
- 傳送HTTP 請求
- 服務端相應HTTP 返回資料
- 瀏覽器拿到響應資料,解析響應內容,把解析的結果展示給使用者
前端效能都包括哪些
這個效能劃分是我從一篇文章上扒下來的,我們這裡主要討論效能監控
前端效能監控-關鍵指標
- 首屏時間
- 白屏時間
- 頁面總下載時間
瀏覽器效能先關內容概述
效能方面我們通過 window.preformance.timing 這個屬性來獲取效能相關引數
window.preformance.timing(記錄了頁面各個狀態的時間戳)
那麼這些引數都是什麼意義,以及它們觸發機制是什麼
我來看一張經典的圖
我們把它翻譯一下
【Prompt for unload】- 使用者跳轉行為(在位址列輸入url後按回車,或者點選a標籤跳轉等)
navigationStart、startTime // 當前瀏覽器視窗的前一個網頁關閉開始執行的時間戳
unloadStart // 前一個頁面unload觸發開始時間戳
【unload】- 前一個頁面unload時間
unloadEnd // 前一個頁面unload觸發結束時間戳
redirectStart // 返回第一個HTTP跳轉開始時的時間戳如果沒有跳轉,或者不是同一個域名內部的跳轉,則返回值為0
【redirect】- 重定向
redirectEnd // 返回最後一個HTTP跳轉結束時(即跳轉回應的最後一個位元組接受完成時)的時間戳,如果沒有跳轉,或者不是同一個域名內部的跳轉,則返回值為0
fetchStart // 返回瀏覽器準備使用HTTP請求讀取文件時的時間戳。該事件在網頁查詢本地快取之前發生
【App cache】- 網頁查詢本地快取
domainLookupStart // 返回域名查詢開始時的時間戳。如果使用持久連線,或者資訊是從本地快取獲取的,則返回值等同於fetchStart屬性的值
【DNS】- 域名查詢
domainLookupEnd // 返回域名查詢結束時的時間戳。如果使用持久連線,或者資訊是從本地快取獲取的,則返回值等同於fetchStart屬性的值
connectStart // 返回建立TCP連結開始向伺服器傳送時的時間戳。如果使用持久連線(persistent connection),則返回值等同於fetchStart屬性的值
【TCP】
secureConnectionStart // 它的值是安全連線握手之前的時刻。如果該屬性不可用,則返回undefined。如果該屬性可用,但沒有使用HTTPS,則返回0
connectEnd // 返回瀏覽器與伺服器之間的連線建立時的時間戳。如果建立的是持久連線,則返回值等同於fetchStart屬性的值。連線建立指的是所有握手和認證過程全部結束
回瀏覽器與伺服器開始安全連結的握手時的時間戳。如果當前網頁不要求安全連線,則返回0
requestStart // 返回瀏覽器向伺服器發出HTTP請求時(或開始讀取本地快取時)的時間戳
【Request】 - 網路請求
responseStart // 返回瀏覽器從伺服器收到(或從本地快取讀取)第一個位元組時的時間戳
【Response】
responseEnd // 返回瀏覽器從伺服器收到(或從本地快取讀取)最後一個位元組時(如果在此之前HTTP連線已經關閉,則返回關閉時)的時間戳
domLoading // 返回當前網頁DOM結構開始解析時(即Document.readyState屬性變為“loading”、相應的readystatechange事件觸發時)的時間戳
【Processing】
domInteractive // 返回當前網頁DOM結構結束解析、開始載入內嵌資源時(即Document.readyState屬性變為“interactive”、相應的readystatechange事件觸發時)的時間戳
domContentLoadedEventStart // 返回當前網頁DOMContentLoaded事件發生時(即DOM結構解析完畢、所有指令碼開始執行時)的時間戳
domContentLoadedEventEnd // 返回當前網頁所有需要執行的指令碼執行完成時的時間戳
domComplete // 返回當前網頁DOM結構生成時(即Document.readyState屬性變為“complete”,以及相應的readystatechange事件發生時)的時間戳
loadEventStart // 返回當前網頁load事件的回撥函式開始時的時間戳。如果該事件還沒有發生,返回0
【onLoad】- window.onLoad觸發
loadEventEnd // 返回當前網頁load事件的回撥函式執行結束時的時間戳。如果該事件還沒有發生,返回0。通過while迴圈持續判斷直到loadEventEnd>0則表示完全載入完畢了!網路不再有任何資料請求、dom也渲染完畢了
複製程式碼
注意
由於window.preformance.timing是一個在不同階段,被不停修正的一個引數物件,所以,建議在window.onload中進行效能資料讀取和上報
關鍵資料獲取
- 首屏時間:計算起來比較麻煩,後文會詳述
- 白屏時間:responseEnd - navigationStart
- 頁面總下載時間:loadEventEnd - navigationStart
其他資料:
- DNS解析耗時:domainLookupEnd - domainLookupStart
- TCP連結耗時:connectEnd - connectStart
- 首包請求耗時:responseEnd - responseStart
- dom解釋耗時:domComplete - domInteractive
- 使用者可操作時間:domContentLoadedEventEnd - navigationStart
- ...
首屏時間計算
目前業內對首屏時間方式並不統一,常見的有下面幾種計算方式:
- 利用首屏中最後一張圖片載入完成的時間來當做首屏時間(適合首屏元素由服務端渲染)
方法:給頁面所有的img繫結onload事件,用來記錄圖片載入時間;在window.onload中,計算每一個img的offsetTop;把符合首屏高度的圖片資料收集起來,計算最大onload時間
- 圖片相似度比較法,通過比較連續截圖影像的畫素點變化趨勢確定首屏時間(適合非同步請求資料渲染的場景)
方法:通過html2canvas外掛,每100ms擷取螢幕;然後獲取螢幕九宮格每一格中心點的,獲取紅色通道的畫素相加得到一個值,通過不斷截圖和比較這個求和的值,監控出首屏是否載入完畢。(截圖影像相似度比較的方法最為科學和直觀,但是比較消耗本地裝置的執行資源。而且由於比較複雜的運算,會影響到頁面邏輯指令碼執行的效能)
- 首屏模組標籤標記法(頁面多數非同步請求渲染)
方法:在 HTML 文件中對應首屏內容的標籤結束位置,使用內聯的 JavaScript 程式碼記錄當前時間戳
- 自定義模組內容計演算法(自定義每個頁面太複雜)
方法:通過自定義模組內容,來簡化計算首屏時間
- 用onLoad或者domReady觸發時間表示首屏時間
我們的首屏計算方式
首先我們要介紹一下window.performance.getEntries()方法,因為後面會用到
window.performance.getEntries()
呼叫後會獲取頁面載入資源和網路請求資訊
拿到之後是個陣列,裡面包含了頁面所有的靜態資源、網路請求相關資訊,我們通過initiatorType來獲取每一個元素的屬性
“xmlhttprequest”表示的就是介面請求
首屏計算方式
- 預設值為:domContentLoadedEventEnd - navigationStart
- 如果是vue專案,採用mounted觸發時間作為計算分界點。
- 如果是react專案,採用componentDidMount觸發時間作為計算分界點。
- 遍歷window.performance.getEntries()所有內容,過濾mounted、componentDidMount之前發出的所有請求內容,如果有xmlhttprequest,則採用最後返回response的時間作為首屏時間
- 如果是ssr專案,直接採用預設計算方式(domContentLoadedEventEnd - navigationStart)
可能大家會有疑問,我們首屏時間的計算中,不包含渲染和圖片載入時間?
確實,我們這麼做有兩個前提:
- 通常情況下我們認為瀏覽器渲染時間會很快
- 頁面載入的圖片必須進行擷取(這個由各業務線自己控制)
所以這種首屏計算方式,更像是“首屏資料開始渲染時間”(因為此時已經拿到了首屏渲染所有需要的資料)
在圖片過多、過大時候是會有一定程度的誤差
我們為什麼這麼做?
- 為了實現自動統計,且不希望效能統計的邏輯被強制注入到的業務程式碼中
- 儘可能的接近真實的首屏載入時間
- 在不過多佔用瀏覽器首屏載入效能的情況下完成資料收集
首屏時間目前各家公司的計算方式不盡相同,但是所採用的,一定是在符合各自公司真實情況的前提下,最接近於首屏時間的計算方式。
其實資料準確性和收集資料的效能開銷,是一種博弈,對於公司來講,應該選擇最合適的方式。