前端效能優化 —— 前端效能分析
作者:ouven https://my.oschina.net/zhangstephen/blog/1601380
前端效能優化是一個很寬泛的概念,本書前面的部分也多多少少提到一些前端優化方法,這也是我們一直在關注的一件重要事情。配合各種方式、手段、輔助系統,前端優化的最終目的都是提升使用者體驗,改善頁面效能,我們常常竭盡全力進行前端頁面優化,但卻忽略了這樣做的效果和意義。先不急於探究前端優化具體可以怎樣去做,先看看什麼是前端效能,應該怎樣去了解和評價前端頁面的效能。
通常前端效能可以認為是使用者獲取所需要頁面資料或執行某個頁面動作的一個實時性指標,一般以使用者希望獲取資料的操作到使用者實際獲得資料的時間間隔來衡量。例如使用者希望獲取資料的操作是開啟某個頁面,那麼這個操作的前端效能就可以用該使用者操作開始到螢幕展示頁面內容給使用者的這段時間間隔來評判。使用者的等待延時可以分成兩部分:可控等待延時和不可控等待延時。可控等待延時可以理解為能通過技術手段和優化來改進縮短的部分,例如減小圖片大小讓請求載入更快、減少HTTP請求數等。不可控等待延時則是不能或很難通過前後端技術手段來改進優化的,例如滑鼠點選延時、CPU計算時間延時、ISP(Internet Service Provider,網際網路服務提供商)網路傳輸延時等。所以要知道的是,前端中的所有優化都是針對可控等待延時這部分來進行的,下面來了解一下如何獲取和評價一個頁面的具體效能。
前端效能測試
獲取和衡量一個頁面的效能,主要可以通過以下幾個方面:Performance Timing API、Profile工具、頁面埋點計時、資源載入時序圖分析。
一、Performance Timing API
Performance Timing API是一個支援Internet Explorer 9以上版本及WebKit核心瀏覽器中用於記錄頁面載入和解析過程中關鍵時間點的機制,它可以詳細記錄每個頁面資源從開始載入到解析完成這一過程中具體操作發生的時間點,這樣根據開始和結束時間戳就可以計算出這個過程所花的時間了。
圖1為W3C標準中Performance Timing資源載入和解析過程記錄各個關鍵點的示意圖,瀏覽器中載入和解析一個HTML檔案的詳細過程先後經歷unload、redirect、App Cache、DNS、TCP、Request、Response、Processing、onload幾個階段,每個過程開始和結束的關鍵時間戳瀏覽器已經使用performance.timing來記錄了,所以根據這個記錄並結合簡單的計算,我們就可以得到頁面中每個過程所消耗的時間。
圖1 performance API關鍵時間點記錄
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事件耗時和整個載入過程耗時等,不過在頁面效能獲取時我們可以儘量獲取更詳細的資料資訊,以供後面分析。除了資源載入解析的關鍵點計時,performance還提供了一些其他方面的功能,我們可以根據具體需要進行選擇使用。
performance.memory // 記憶體佔用的具體資料
performance.now() // performance.now()方法返回當前網頁自performance.timing到現在的時間,可以精確到微秒,用於更加精確的計數。但實際上,目前網頁效能通過毫秒來計算就足夠了。
performance.getEntries() // 獲取頁面所有載入資源的performance timing情況。瀏覽器獲取網頁時,會對網頁中每一個物件(指令碼檔案、樣式表、圖片檔案等)發出一個HTTP請求。performance.getEntries方法以陣列形式返回所有請求的時間統計資訊。
performance.navigation // performance還可以提供使用者行為資訊,例如網路請求的型別和重定向次數等,一般都存放在performance.navigation物件裡面。
performance.navigation.redirectCount // 記錄當前網頁重定向跳轉的次數。
參考資料:https://www.w3.org/TR/resource-timing/。
二、 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()
});
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檔案和頁面中非關鍵性圖片等內容延後載入。如果因為某個資源的載入十分耗時而阻塞了頁面的內容展示,那就要著重考慮。所以,我們需要通過資源載入時序圖來輔助分析頁面上資源載入順序的問題。
圖2
圖2為使用Fiddler獲取瀏覽器訪問地址 http://www.jixianqianduan.com 時的資源載入時序圖。根據此圖,我們可以很直觀地看到頁面上各個資源載入過程所需要的時間和先後順序,有利於找出載入過程中比較耗時的檔案資源,幫助我們有針對性地進行優化。
覺得本文對你有幫助?請分享給更多人。