在進行前端優化的時候,咱們須要關注各類性能相關的指標,瞭解這些指標表明的含義才能更好地進行性能優化。javascript
對於前面兩個指標,咱們能夠簡單地經過Performance API來進行統計和計算css
function getInitTime() { if(window.performace && performance.timing) { // 計算DOMContentedLoaded 耗時 const DOMContentLoadedTime = performance.timing.domInteractive - performance.timing.navigationStart // 計算Load耗時 const loadTime = performance.timing.loadEventStart- performance.timing.navigationStart }}複製代碼
可是對於初始化首屏耗時,這就得看這個指標怎麼定義。前端
在咱們團隊,對這個指標是頁面初始化加載須要的所有數據渲染到頁面以後,頁面對於用戶處於一種數據 ready,而且可進行交互操做的狀態時,這個時間點和 performance.timing.navigationStart 的差值。vue
舉個栗子🌰java
在這張首屏初始化時序圖中,咱們能夠看到藍線表明的是 DOMContentLoaded 的時間點,前面的都是靜態資源 css、js,圖片等的加載,而紅線則表明 Load 時間點,在藍色和紅色之間,有未加載完的資源,也有前端發起的請求,在紅線以後,有的請求尚未返回,可是這個接口的數據是首屏渲染所必須的。node
假如咱們對某個頁面的首屏耗時進行計算,有可能由於一些接口慢而致使在 Load 以後還在加載,頁面還處於 loading 的狀態,而用戶不能看到首屏須要的所有數據。(因此纔要優化,給我所有的 mock 數據,我能秒開你信不信)ios
既然前端須要的首屏數據來自接口,那咱們就先對每一個接口進行打點,計算接口的耗時,以 axios 爲例面試
const timeMap = {}// 請求攔截器axios.interceptors.request.use(config => { if (timeMap[config.url]) { timeMap[config.url].startTime = new Date().getTime(); } else { timeMap[config.url] = { startTime: 0, endTime: 0, total: 0 } timeMap[config.url].startTime = new Date().getTime(); } return config;}, function (error) { return Promise.reject(error);});// 響應攔截器axios.interceptors.response.use(response => { if (timeMap[response.config.url] && timeMap[response.config.url].startTime) { timeMap[response.config.url].endTime = new Date().getTime(); timeMap[response.config.url].total = timeMap[response.config.url].endTime - timeMap[response.config.url].startTime; } console.log(timeMap) return response; // 響應返回參數}, (responseError) => { // 響應錯誤 console.log("responseError", responseError);});複製代碼
這樣咱們就能夠拿到每一個具體的接口的耗時,而後初始化的時候,能夠拿最後一個接口的 endTime 來做爲首屏的耗時點。vue-cli
事情真的就這麼簡單嗎?咱們想一下,數據拿回來以後,咱們要作的就是把數據渲染到頁面上,瀏覽器渲染數據的過程也是須要消耗時間的,也就是說,實際消耗的時間 > 最慢接口的結束時間axios
假如咱們能夠拿到 DOM 變化或者 DOM 渲染的時間點,那麼是否是就解決了。這時候,MutationObverser 就派上用場了。
DOM 規範中的
MutationObserver()
構造函數——是MutationObserver
接口內容的一部分——建立並返回一個新的觀察器,它會在觸發指定 DOM 事件時,調用指定的回調函數。MutationObserver 對 DOM 的觀察不會當即啓動;而必須先調用observe()
方法來肯定,要監聽哪一部分的 DOM 以及要響應哪些更改。
當即擼出以下代碼:
if (window.MutationObserver) { let changeTime = 0; //對DOM進行觀察 const observer = new MutationObserver((mutationList, mutationObserver) => { changeTime = new Date().getTime(); console.log(mutationList) console.log('dom Change:', (changeTime - performance.timing.navigationStart) / 1000); }); const node = document.querySelector('#app'); //觀察子節點的變化 observer.observe(node, { childList: true, subtree: true }) //必定時間以後取消對DOM的觀察 setTimeout(() => { observer.disconnect(); }, timeOut) }複製代碼
咱們能夠在控制檯看到每一次DOM發生變化時,頁面有哪些元素進行了建立,銷燬和更新
既然這樣,當數據返回的時候,也就是 DOM 的變化波動最爲激烈的時候,考慮 DOM 暴增的那個點做爲首屏耗時的時間點進行統計。也就是 mutationList 中 addNotes 數量最多的那個點進行統計。
可是,若是說初始化首屏數據依賴於兩個接口,接口 1 比較快,接口 2 比較慢,而接口 1 包含大量數據,接口 2 包含了少許數據,那麼咱們按照 DOM 暴增的點進行統計,可能就會比實際提早,因此咱們應該兩種思路結合起來。在頁面所有接口都返回以後,DOM 變化的那個點做爲首屏的點。
對上面的代碼進行整合修改
const timeMap = {}// 請求攔截器axios.interceptors.request.use(config => { if (timeMap[config.url]) { timeMap[config.url].startTime = new Date().getTime(); } else { timeMap[config.url] = { startTime: 0, endTime: 0, total: 0 } timeMap[config.url].startTime = new Date().getTime(); } return config;}, function (error) { return Promise.reject(error);});// 響應攔截器axios.interceptors.response.use(response => { if (timeMap[response.config.url] && timeMap[response.config.url].startTime) { timeMap[response.config.url].endTime = new Date().getTime(); timeMap[response.config.url].total = timeMap[response.config.url].endTime - timeMap[response.config.url].startTime; } console.log(timeMap) return response; // 響應返回參數}, (responseError) => { // 響應錯誤 console.log("responseError", responseError);});if (window.MutationObserver) { let changeTime = 0; //對DOM進行觀察 const observer = new MutationObserver((mutationList, mutationObserver) => { let isRequestAllEnd = true; const urlKeys = Object.keys(timeMap); urlKeys.forEach((d) => { if(!d.endTime) { isRequestAllEnd = false; } }) if(isRequestAllEnd) { changeTime = new Date().getTime(); console.log('init time', (changeTime - performance.timing.navigationStart) / 1000); observer.disconnect(); } }); const node = document.querySelector('#app'); //觀察子節點的變化 observer.observe(node, { childList: true, subtree: true }); }複製代碼
關注公衆號,回覆「加羣」,進入學習交流羣