前端效能優化是一個很寬泛的概念,有很多教程都有前端效能優化的方法,這也是我們一直在關注的一件重要事情。配合各種方式、手段、輔助系統,前端優化的最終目的都是提升使用者體驗,改善頁面效能,我們常常竭盡全力進行前端頁面優化,但卻忽略了這樣做的效果和意義。先不急於探究前端優化具體可以怎樣去做,先看看什麼是前端效能,應該怎樣去了解和評價前端頁面的效能。
通常前端效能可以認為是使用者獲取所需要頁面資料或執行某個頁面動作的一個實時性指標,一般以使用者希望獲取資料的操作到使用者實際獲得資料的時間間隔來衡量。例如使用者希望獲取資料的操作是開啟某個頁面,那麼這個操作的前端效能就可以用該使用者操作開始到螢幕展示頁面內容給使用者的這段時間間隔來評判。使用者的等待延時可以分成兩部分:可控 等待延時和 不可控 等待延時。可控 等待延時可以理解為能通過技術手段和優化來改進縮短時間的部分,例如減小圖片大小讓請求載入更快、減少 HTTP 請求數等。不可控 等待延時則是不能或很難通過前後端技術手段來改進優化的,例如滑鼠點選延時、CPU 計算時間延時、ISP(Internet Service Provider,網際網路服務提供商) 網路傳輸延時等。所以要知道的是,前端中的所有優化都是針對 可控等待延時 這部分來進行的,下面來了解一下如何獲取和評價一個頁面的具體效能。
前端效能測試
獲取和衡量一個頁面的效能,主要可以通過以下幾個方面:Performance Timing API
、Prpfile
工具、頁面埋點計時、資源載入時序圖分析。
Performance Timing API
Performance Timing API 是一個支援 Internet Explorer 9
以上版本及 WebKit
核心瀏覽器中用於記錄頁面載入和解析過程中關鍵時間點的機制,它可以詳細記錄每個頁面資源從開始載入到解析完成這一過程中具體操作發生的時間點,這樣根據開始和結束時間戳就可以計算出這個過程所花的時間了。
圖 1-1為 W3C標準中 Performance Timing
資源載入和解析過程記錄各個關鍵點的示意圖,瀏覽器中載入和解析一個 HTML 檔案的詳細過程先後經歷 unload、redirect、App Cache、DNS、TCP、Request、Response、Processing、onload 幾個階段,每個過程中開始和結束的關鍵時間戳瀏覽器已經使用 performance.timing
來記錄了,所以根據這個記錄並結合簡單的計算,我們就可以得到頁面中每個過程所消耗的時間。
function performanceTest(){
let timing = performance.timing,
readyStart = timing.fetchStart - timing.navigationStart,
redirectTime = timing.redirectEnd - timing.redirectStart,
appcacheTime = timing.domainLookupStart - timing.fetchStart,
unloadEventTime = timing.unloadEventEnd - timing.unloadEventStart,
lookupDomainTime = timing.domainLookupEnd - timing.domainLookupStart,
connectTime = timing.connectEnd - timing.connectStart,
requestTime = timing.responseEnd - timing.requestStart,
initDomTreeTime = timing.domInteractive - timing.responseEnd,
domReadyTime = timing.domComplete - timing.domInteractive,
loadEventTime = timing.loadEventEnd - timing.loadEventStart,
loadTime = timing.loadEventEnd - timing.navigationStart;
console.log('準備新頁面時間耗時:'+readyStart);
console.log('redirect 重定向耗時:'+redirectTime);
console.log('Appcache 耗時'+appcacheTime);
console.log('unload 前文件耗時:'+unloadEventTime);
console.log('DNS 查詢耗時:'+lookupDomainTime);
console.log('TCP 連線耗時:'+connectTime);
console.log('request 請求耗時:'+requestTime);
console.log('請求完畢至DOM載入:'+initDomTreeTime);
console.log('解析DOM樹耗時:'+domReadyTime);
console.log('Load事件耗時:'+loadEventTime);
console.log('載入時間耗時:'+loadTime);
}
複製程式碼
通過上面的時間戳計算可以得到幾個關鍵步驟所消耗的時間,對前端有意義的幾個過程主要是解析 DOM 樹耗時、load 事件耗時和整個載入過程耗時等,不過在頁面效能獲取時我們可以儘量獲取更詳細的資料資訊,以供後面分析。除了資源載入解析的關鍵點計時,preformance 還提供了一些其他方面的功能,我們可以根據具體需要進行選擇使用。
performance.memory //記憶體佔用的具體資料
performance.now() //performance.now()方法返回當前網頁自performance.timing到現在的時間,可以精確到微秒,用於更加精確的計數。但實際上,目前網頁效能通過毫秒來計算就足夠了
performance.getEntries() //獲取頁面所有載入資源的performance timing 情況。瀏覽器獲取網頁時,會對網頁中每一個物件(指令碼檔案、樣式表、圖片檔案等)發出一個HTTP請求。performance.getEntries 方法以陣列形式返回所有請求的時間統計資訊
performance.navigation //performance 還可以提供使用者行為資訊,例如網路請求的型別和重定向次數等,一般都存放在performance.navigation物件裡面
performance.navigation.redirectCount //記錄當前網頁重定向跳轉的次數
複製程式碼
Profile 工具
Performance Timing API 描述了頁面資源從載入到解析各個階段的執行關鍵點時間記錄,但是無法統計 JavaScript 執行過程中系統資源的佔用情況。Profile 是 Chrome 和 Firefox 等標準瀏覽器提供的一種用於測試頁面指令碼執行時系統記憶體和 CPU 資源佔用情況的 API,以 Chrome 瀏覽器為例,結合 Profile,可以實現以下幾個功能。
1.分析頁面指令碼執行過程中最耗資源的操作
2.記錄頁面指令碼執行過程中 JavaScript 物件消耗的記憶體與堆疊的使用情況
3.檢測頁面指令碼執行過程中 CPU 佔用情況
使用 console.profile()
和console.profileEnd()
就可以分析中間一段程式碼執行時系統的記憶體或 CPU 資源的消耗情況,然後配合瀏覽器的 Profile 檢視比較消耗系統記憶體或 CPU 資源的操作,這樣就可以有針對性的進行優化了。
console.profile();
//TODOS,需要測試的頁面邏輯動作
for(let i = 0; i < 100000; i ++){
console.log(i * i);
}
console.profileEnd();
複製程式碼
頁面埋點計時
使用Profile可以在一定程度上幫助我們分析頁面的效能,但缺點是不夠靈活。實際專案中,我們不會多關注頁面的記憶體或CPU資源的消耗情況,因為JavaScript有自動記憶體回收機制。我們關注更多的是頁面指令碼邏輯執行的時間。除了Performance Timing 的關鍵過程耗時計算,我們還希望檢測程式碼的具體解析或執行時間,這就不能寫很多的console.profile()
和console.profileEnd()
來逐段實現,為了更加簡單地處理這種情況,往往選擇通過指令碼埋點計時的方式來統計沒部分程式碼的執行時間。
頁面 JavaScript 埋點計時比較容易實現,和 Performance Timing 記錄時間戳有點類似,我們可以記錄 JavaScript 程式碼開始執行的時間戳,後面在需要記錄的地方埋點記錄結束時的時間戳,最後通過差值來計算一段 HTML 解析或 JavaScript 解析執行的時間。為了方便操作,可以將某個操作開始和結束的時間戳記錄到一個陣列中,然後分析陣列之間的間隔就得到每個步驟的執行時間,下面來看一個時間點記錄和分析的例子。
let timeList = [];
function addTime(tag) {timeList.push({"tag":tag,"time":+new Date()});}
addTime("loading");
timeList.push({"tag":"load","time":+new Date()});
//TODOS,load載入時的操作
timeList.push({"tag":"load","time":+new Date()});
timeList.push({"tag":"process","time":+new Date()});
//TODOS,process處理時的操作
timeList.push({"tag":"process","time":+new Date()});
console.log(parseTime(timeList)); //輸出{load:時間毫秒數,process: 時間毫秒數}
function parseTime(time) {
let timeStep={},
endTime;
for(let i = 0,len = time.length; i < len; i ++){
if(!time[i]) continue;
endTime = {};
for(let j = i+1; j < len; j++){
if(time[j] && time[i].tag == time[j].tag){
endTime.tag = time[j].tag;
endTime.time = time[j].time;
time[j] = null;
}
}
if(endTime.time >= 0 && endTime.tag){
timeStep[endTime.tag] = endTime.time - time[i].time;
}
}
return timeStep;
}
複製程式碼
這種方式常常在移動端頁面中使用,因為移動端瀏覽器 HTML 解析和 JavaScript 執行相對較慢,通常為了效能優化,我們需要找到頁面中執行 JavaScript 耗時的操作,如果將關鍵 JavaScript 的執行過程進行埋點計時並上報,就可以輕鬆找出 JavaScript 執行慢的地方,並有針對性地進行優化。
資源載入時序圖
我們還可以藉助瀏覽器或其他工具的資源載入時序圖來幫助分析頁面資源載入過程中的效能問題。這種方法可以粗粒度地巨集觀分析瀏覽器的所有資原始檔請求耗時和檔案載入順序情況,如保證 CSS 和資料請求等關鍵資源優先載入,JavaScript 檔案和頁面中非關鍵性圖片等內容延後載入。如果因為某個資源的載入十分耗時而阻塞了頁面的內容展示,那就要著重考慮。所以,我們需要通過資源載入時序圖來輔助分析頁面上資源載入順序的問題。
圖 1-2 為使用 Fiddler 抓取瀏覽器訪問地址時的資源載入時序圖,圖 1-3 為 Chrome 瀏覽器訪問貓眼電影官網時的資源載入時序圖.根據資源載入時序圖我們可以很直觀地看到頁面上各個資源載入過程所需要的時間和先後順序,有利於找出載入過程中比較耗時的檔案資源,幫助我們有針對性地進行優化。
圖1-2 Fiddler 頁面載入檔案資源時序圖 圖1-3 Chrome瀏覽器 貓眼電影官網頁面載入檔案資源時序圖總結
通過上文介紹的利用
Performance Timing API
、Prpfile
工具、頁面埋點計時、資源載入時序圖分析幾個方面來獲取和衡量一個頁面的效能,找出載入過程中比較耗時的檔案資源,幫助我們有針對性地進行優化。醫病必須診斷找到病因,前端效能測試在前端效能優化中屬於診斷工作,只有找到病因,才能進行鍼對性治療優化。在此歡迎大家加入QQ前端技術交流群544587175
,在後續我會從桌面瀏覽器和移動端瀏覽器兩個方面詳細講解不同終端的優化策略,敬請關注......