前端性能優化是一個很寬泛的概念,有很多教程都有前端性能優化的方法,這也是我們一直在關注的一件重要事情。配合各種方式、手段、輔助系統,前端優化的最終目的都是提升用戶體驗,改善頁面性能,我們常常竭盡全力進行前端頁面優化,但卻忽略了這樣做的效果和意義。先不急于探究前端優化具體可以怎樣去做,先看看什么是前端性能,應該怎樣去了解和評價前端頁面的性能。
通常前端性能可以認為是用戶獲取所需要頁面數據或執行某個頁面動作的一個實時性指標,一般以用戶希望獲取數據的操作到用戶實際獲得數據的時間間隔來衡量。例如用戶希望獲取數據的操作是打開某個頁面,那么這個操作的前端性能就可以用該用戶操作開始到屏幕展示頁面內容給用戶的這段時間間隔來評判。用戶的等待延時可以分成兩部分:可控 等待延時和 不可控 等待延時。可控 等待延時可以理解為能通過技術手段和優化來改進縮短時間的部分,例如減小圖片大小讓請求加載更快、減少 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 瀏覽器訪問貓眼電影官網時的資源加載時序圖.根據資源加載時序圖我們可以很直觀地看到頁面上各個資源加載過程所需要的時間和先后順序,有利于找出加載過程中比較耗時的文件資源,幫助我們有針對性地進行優化。
總結
通過上文介紹的利用
Performance Timing API
、Prpfile
工具、頁面埋點計時、資源加載時序圖分析幾個方面來獲取和衡量一個頁面的性能,找出加載過程中比較耗時的文件資源,幫助我們有針對性地進行優化。醫病必須診斷找到病因,前端性能測試在前端性能優化中屬于診斷工作,只有找到病因,才能進行針對性治療優化。在此歡迎大家加入QQ前端技術交流群544587175
,在后續我會從桌面瀏覽器和移動端瀏覽器兩個方面詳細講解不同終端的優化策略,敬請關注......