閱讀目錄
一:什麼是Performance?
Performance是前端效能監控的API。它可以檢測頁面中的效能,W3C效能小組引入進來的一個新的API,它可以檢測到白屏時間、首屏時間、使用者可操作的時間節點,頁面總下載的時間、DNS查詢的時間、TCP連結的時間等。因此我們下面來學習下這個API。
那麼在學習之前,前端效能最主要的測試點有如下幾個:
白屏時間:從我們開啟網站到有內容渲染出來的時間點。
首屏時間:首屏內容渲染完畢的時間節點。
使用者可操作時間節點:domready觸發節點。
總下載時間:window.onload的觸發節點。
我們現在在html中來簡單的使用下performance的基本程式碼:如下程式碼所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <script type="text/javascript"> var performance = window.performance || window.msPerformance || window.webkitPerformance; if (performance) { console.log(performance); } </script> </body> </html>
然後在瀏覽器下會列印如下 performance基本資訊如下:
如上可以看到,performance包含三個物件,分別為 memory、navigation、timing. 其中 memory 是和記憶體相關的,navigation是指來源相關的,也就是說從那個地方跳轉過來的。timing是關鍵點時間。下面我們來分別介紹下該物件有哪些具體的屬性值。
performance.memory 含義是顯示此刻記憶體佔用的情況,從如上圖可以看到,該物件有三個屬性,分別為:
jsHeapSizeLimit 該屬性代表的含義是:記憶體大小的限制。
totalJSHeapSize 表示 總記憶體的大小。
usedJSHeapSize 表示可使用的記憶體的大小。
如果 usedJSHeapSize 大於 totalJSHeapSize的話,那麼就會出現記憶體洩露的問題,因此是不允許大於該值的。
performance.navigation 含義是頁面的來源資訊,該物件有2個屬性值,分別是:redirectCount 和 type。
redirectCount: 該值的含義是:如果有重定向的話,頁面通過幾次重定向跳轉而來,預設為0;
type:該值的含義表示的頁面開啟的方式。預設為0. 可取值為0、1、2、255.
0(TYPE_NAVIGATE):表示正常進入該頁面(非重新整理、非重定向)。
1(TYPE_RELOAD):表示通過 window.location.reload 重新整理的頁面。如果我現在重新整理下頁面後,再來看該值就變成1了。
2(TYPE_BACK_FORWARD ):表示通過瀏覽器的前進、後退按鈕進入的頁面。如果我此時先前進下頁面,再後退返回到該頁面後,檢視列印的值,發現變成2了。
255(TYPE_RESERVED): 表示非以上的方式進入頁面的。
如下圖所示:
performance.onresourcetimingbufferfull; 如上截圖也有這個屬性的,該屬性的含義是在一個回撥函式。該回撥函式會在瀏覽器的資源時間效能緩衝區滿了的時候會執行的。
performance.timeOrigin:是一系列時間點的基準點,精確到萬分之一毫秒。如上截圖該值為:1559526951495.139,該值是
一個動態的,重新整理下,該值是會發生改變的。
performance.timing:是一系列關鍵時間點,它包含了網路、解析等一系列的時間資料。
為了方便,從網上弄了一張圖片過來,來解析下各個關鍵時間點的含義如下所示:
按照如上圖的順序,我們來分別看下各個欄位的含義如下:
navigationStart: 含義為:同一個瀏覽器上一個頁面解除安裝結束時的時間戳。如果沒有上一個頁面的話,那麼該值會和fetchStart的值相同。
redirectStart: 該值的含義是第一個http重定向開始的時間戳,如果沒有重定向,或者重定向到一個不同源的話,那麼該值返回為0.
redirectEnd: 最後一個HTTP重定向完成時的時間戳。如果沒有重定向,或者重定向到一個不同的源,該值也返回為0.
fetchStart: 瀏覽器準備好使用http請求抓取文件的時間(發生在檢查本地快取之前)。
domainLookupStart: DNS域名查詢開始的時間,如果使用了本地快取話,或 持久連結,該值則與fetchStart值相同。
domainLookupEnd: DNS域名查詢完成的時間,如果使用了本地快取話,或 持久連結,該值則與fetchStart值相同。
connectStart: HTTP 開始建立連線的時間,如果是持久連結的話,該值則和fetchStart值相同,如果在傳輸層發生了錯誤且需要重新建立連線的話,那麼在這裡顯示的是新建立的連結開始時間。
secureConnectionStart: HTTPS 連線開始的時間,如果不是安全連線,則值為 0
connectEnd:HTTP完成建立連線的時間(完成握手)。如果是持久連結的話,該值則和fetchStart值相同,如果在傳輸層發生了錯誤且需要重新建立連線的話,那麼在這裡顯示的是新建立的連結完成時間。
requestStart: http請求讀取真實文件開始的時間,包括從本地讀取快取,連結錯誤重連時。
responseStart: 開始接收到響應的時間(獲取到第一個位元組的那個時候)。包括從本地讀取快取。
responseEnd: HTTP響應全部接收完成時的時間(獲取到最後一個位元組)。包括從本地讀取快取。
unloadEventStart: 前一個網頁(和當前頁面同域)unload的時間戳,如果沒有前一個網頁或前一個網頁是不同的域的話,那麼該值為0.
unloadEventEnd: 和 unloadEventStart 相對應,返回是前一個網頁unload事件繫結的回撥函式執行完畢的時間戳。
domLoading: 開始解析渲染DOM樹的時間。
domInteractive: 完成解析DOM樹的時間(只是DOM樹解析完成,但是並沒有開始載入網頁的資源)。
domContentLoadedEventStart:DOM解析完成後,網頁內資源載入開始的時間。
domContentLoadedEventEnd: DOM解析完成後,網頁內資源載入完成的時間。
domComplete: DOM樹解析完成,且資源也準備就緒的時間。Document.readyState 變為 complete,並將丟擲 readystatechange 相關事件。
loadEventStart: load事件傳送給文件。也即load回撥函式開始執行的時間,如果沒有繫結load事件,則該值為0.
loadEventEnd: load事件的回撥函式執行完畢的時間,如果沒有繫結load事件,該值為0.
如上就是各個值的含義了,大家簡單的看下,瞭解下就行了,不用過多的折騰。在使用這些值來計算白屏時間、首屏時間、使用者可操作的時間節點,頁面總下載的時間、DNS查詢的時間、TCP連結的時間等之前,我們可以先看下傳統方案是如何做的?
傳統方案
在該API出現之前,我們想要計算出如上前端效能的話,我們需要使用時間戳來大概估計下要多長時間。比如使用:(new Date()).getTime() 來計算之前和之後的值,然後兩個值的差值就是這段的時間已用的時間。但是該方法有誤差,不準確。下面我們來看看傳統的方案如下:
1.1 白屏時間
白屏時間:是指使用者進入該網站(比如重新整理頁面、跳轉到新頁面等通過該方式)的時刻開始計算,一直到頁面內容顯示出來之前的時間節點。如上圖我們可以看到,這個過程包括dns查詢、建立tcp連結、傳送首個http請求等過程、返回html文件。
比如如下程式碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> <script type="text/javascript"> var startTime = (new Date()).getTime(); </script> <link href="xx1.css" rel="stylesheet" /> <link href="xx2.css" rel="stylesheet" /> <script type="text/javascript" src="xx1.js"></script> <script type="text/javascript" src="xx2.js"></script> <script type="text/javascript"> var endTime = (new Date()).getTime(); </script> </head> <body> <script type="text/javascript"> </script> </body> </html>
如上程式碼,endTime - startTime 的值就可以當作為白屏時間的估值了。
1.2 首屏時間
要獲取首屏時間的計算,首先我們要知道頁面載入有2種方式:
1. 載入完資原始檔後通過js動態獲取介面資料,然後資料返回回來渲染內容。
因此會有如下所示的資訊圖:如下所示:
2. 同構直出前端頁面,如下所示:
css資原始檔載入時間的計算,我們可以如上圖所示:t2-t1 就是所有的css載入的時間。
因此假如我們現在的專案檔案程式碼index.html 程式碼如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> <script type="text/javascript"> // 獲取頁面開始的時間 var pageStartTime = (new Date()).getTime(); </script> <link href="xx1.css" rel="stylesheet" /> <link href="xx2.css" rel="stylesheet" /> <script type="text/javascript"> // 獲取載入完成後的css的時間 var cssEndTime = (new Date()).getTime(); </script> <script type="text/javascript" src='./jquery.js'></script> <script type="text/javascript"> // 獲取載入完 jquery外掛的時間 var jsPluginTime = (new Date()).getTime(); </script> </head> <body> <h1>計算時間</h1> <script type="text/javascript"> // 載入js的開始時間 var JsStartTime = (new Date()).getTime(); </script> <script type="text/javascript" src="xx1.js"></script> <script type="text/javascript" src="xx2.js"></script> <script type="text/javascript"> // 載入完成後的js的結束時間 var JsEndTime = (new Date()).getTime(); </script> </body> </html>
如上程式碼,可以獲取頁面開始的時間 pageStartTime, 載入資原始檔css後的時間就是 cssEndTime - pageStartTime,載入jquery外掛的時間 jsPluginTime - cssEndTime 了。但是 js的載入時間難道就是 = JsEndTime - JsStartTime 嗎?這肯定不是的,因為JS還需要有執行的時間的。比如js內部做了很多dom操作,那麼dom操作需要時間的,那麼js載入和執行的時間 = JsEndTime - JsStartTime; 嗎?這也是不對的,因為瀏覽器載入資原始檔是並行的,執行js檔案是序列的。那如果css檔案或jquery檔案發起http請求後一直沒有返回,那麼它會阻塞後續js檔案的執行的。但是此時此刻js檔案載入很早就已經返回了,但是由於伺服器原因或網路原因導致css檔案載入很慢,所以會堵塞js檔案的執行。
因此我們可以總結為:js載入的時間 不等於 JsEndTime - JsStartTime;同理js載入和執行的實際 也不等於 JsEndTime - JsStartTime。正因為有外鏈css中的http請求,它會堵塞js的執行,因此很多網站會把外鏈css檔案改成內聯css檔案程式碼,內聯css程式碼是序列的。比如百度,淘寶官網等。下面我們來看下這兩個網站的原始碼:
百度原始碼:
我們開啟百度搜尋頁面,然後我們右鍵檢視網頁的原始碼如下:
我們再來看下百度搜尋頁面的網路css的請求可以看到如下,同域名下根本就沒有css外鏈操作,所有的css程式碼都是內聯的,我們檢視網路看到就僅僅有一個css外鏈請求,並且該css並不是百度內部的css檔案,如下所示:
淘寶官網原始碼:
我們操作如上所示,開啟淘寶官網原始碼檢視如下所示:
並且我們檢視淘寶官網的網路請求中只看到兩個css外鏈請求,且該兩個外鏈請求並不是淘寶內部的同域名下的css請求,
如下所示:
並且 該css檔名為 new_suggest-min.css 檔案,我通過原始碼搜尋下,並沒有發現該css檔案外鏈,因此可以肯定的是該css檔案是通過js動態載入進去的,如下所示:
切記:如果body下面有多個js檔案的話,並且有ajax動態渲染的檔案的話,那麼儘量讓他放在最前面,因為其他的js載入的時候會阻止頁面的渲染,導致渲染js一直渲染不了,導致頁面的資料會有一段時間是空白的情況。
二:使用 performance.timing 來計算值
performance 物件有一個timing屬性,該屬性包含很多屬性值,我們還是來看看之前的示意圖,如下所示:
從上面的示意圖我們可以看到:
重定向耗時 = redirectEnd - redirectStart; DNS查詢耗時 = domainLookupEnd - domainLookupStart; TCP連結耗時 = connectEnd - connectStart; HTTP請求耗時 = responseEnd - responseStart; 解析dom樹耗時 = domComplete - domInteractive; 白屏時間 = responseStart - navigationStart; DOMready時間 = domContentLoadedEventEnd - navigationStart; onload時間 = loadEventEnd - navigationStart;
如上就是計算方式,為了方便我們現在可以把他們封裝成一個函式,然後把對應的值計算出來,然後我們根據對應的資料值來進行優化即可。
我們這邊來封裝下該js的計算方式, 程式碼如下:
function getPerformanceTiming() { var performance = window.performance; if (!performance) { console.log('您的瀏覽器不支援performance屬性'); return; } var t = performance.timing; var obj = { timing: performance.timing }; // 重定向耗時 obj.redirectTime = t.redirectEnd - t.redirectStart; // DNS查詢耗時 obj.lookupDomainTime = t.domainLookupEnd - t.domainLookupStart; // TCP連結耗時 obj.connectTime = t.connectEnd - t.connectStart; // HTTP請求耗時 obj.requestTime = t.responseEnd - t.responseStart; // 解析dom樹耗時 obj.domReadyTime = t.domComplete - t.domInteractive; // 白屏時間耗時 obj.whiteTime = t.responseStart - t.navigationStart; // DOMready時間 obj.domLoadTime = t.domContentLoadedEventEnd - t.navigationStart; // 頁面載入完成的時間 即:onload時間 obj.loadTime = t.loadEventEnd - t.navigationStart; return obj; } var obj = getPerformanceTiming(); console.log(obj);
三:前端效能如何優化?
1. 在網頁中,css資原始檔儘量內聯,不要外鏈,具體原因上面已經有說明。
2. 重定向優化,重定向有301(永久重定向)、302(臨時重定向)、304(Not Modified)。前面兩種重定向儘量避免。304是用來做快取的。重定向會耗時。
3. DNS優化,如何優化呢?一般有兩點:第一個就是減少DNS的請求次數,第二個就是進行DNS的預獲取(Prefetching)。在PC端正常的解析DNS一次需要耗費 20-120毫秒的時間。因此我們可以減少DNS解析的次數,進而就會減少DNS解析的時間。
第二個就是DNS的預獲取,什麼叫預獲取呢?DNS預獲取就是瀏覽器試圖在使用者訪問連結之前解析域名。比如說我們網站中有很多連結,但是這些連結不在同一個域名下,我們可以在瀏覽器載入的時候就先解析該域名,當使用者真正去點選該預解析的該連結的時候,可以平均減少200毫秒的耗時(是指第一次訪問該域名的時候,沒有快取的情況下)。這樣就能減少使用者的等待時間,提高使用者體驗。
我們下面可以看下淘寶的官網的程式碼如下就進行了DNS的Prefetch了,如下所示:
DNS Prefetch 應該儘量的放在網頁的前面,推薦放在 <meta charset="UTF-8"> 後面。具體使用方法如下:
<link rel="dns-prefetch" href="//xxx.abc.com"> <link rel="dns-prefetch" href="//yyy.def.com"> <link rel="dns-prefetch" href="//bdimg.share.zhix.net">
4. TCP請求優化
TCP的優化就是減少HTTP的請求數量。比如前端資源合併,圖片,資原始檔進行壓縮等這些事情。
在http1.0當中預設使用短連結,也就是客戶端和服務端進行一次http請求的時候,就會建立一次連結,任務結束後就會中斷該連結。那麼在這個過程當中會有3次TCP請求握手和4次TCP請求釋放操作。
在http1.1中,在http響應頭會加上 Connection: keep-alive,該程式碼的含義是:當該網頁開啟完成之後,連結不會馬上關閉,
當我們再次訪問該連結的時候,會繼續使用這個長連線。這樣就減少了TCP的握手次數和釋放次數。只需要建立一次TCP連結即可,比如我們看下百度的請求如下所示:
5. 渲染優化
在我們做vue或react專案時,我們常見的模板頁面是通過js來進行渲染的。而不是同構直出的html頁面,對於這個渲染過程中對於我們首屏就會有很大的損耗,白屏的時間會增加。因此我們可以使用同構直出的方式來進行伺服器端渲染html頁面會比較好,或者我們可以使用一些webpack工具進行html同構直出渲染,webpack渲染可以看這篇文章。
四:Performance中方法
首先我們在控制檯中列印下 performance 中有哪些方法,如下程式碼:
var performance = window.performance; console.log(performance);
如下所示:
4.1 performance.getEntries()
該方法包含了所有靜態資源的陣列列表。
比如我現在html程式碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <script type="text/javascript"> window.onload = function() { var performance = window.performance; console.log(performance); console.log(performance.getEntries()) } </script> </body> </html>
如上程式碼,我頁面上包含一張淘寶cdn上的一張圖片,因為該方法會獲取頁面中所有包含了頁面中的 HTTP 請求。
然後我們在瀏覽器中看到列印performance.getEntries()資訊如下:
該物件的屬性中除了包含資源載入時間,還有如下幾個常見的屬性:
name: 資源名稱,是資源的絕對路徑,如上圖就是淘寶cdn上面的圖片路徑。我們可以通過 performance.getEntriesByName(name屬性值),來獲取該資源載入的具體屬性。
startTime: 開始時間
duration: 表示載入時間,它是一個毫秒數字,只能獲取同域下的時間點,如果是跨域的話,那麼該時間點為0。
entryType: 資源型別 "resource", 還有 "navigation", "mark" 和 "measure" 這三種。
initiatorType: 表示請求的來源的標籤,比如 link標籤、script標籤、img標籤等。
因此我們可以像 getPerformanceTiming 該方法一樣,封裝一個方法,獲取某個資源的時間。如下封裝程式碼方法如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <script type="text/javascript"> // 計算載入時間 function getEntryTiming (entry) { var obj = {}; // 獲取重定向的時間 obj.redirectTime = entry.redirectEnd - entry.redirectStart; // 獲取DNS查詢耗時 obj.lookupDomainTime = entry.domainLookupEnd - entry.domainLookupStart; // 獲取TCP連結耗時 obj.connectTime = entry.connectEnd - entry.connectStart; // HTTP請求耗時 obj.requestTime = entry.responseEnd - entry.responseStart; obj.name = entry.name; obj.entryType = entry.entryType; obj.initiatorType = entry.initiatorType; obj.duration = entry.duration; return obj; } window.onload = function() { var entries = window.performance.getEntries(); console.log(entries); entries.forEach(function(item) { if (item.initiatorType) { var curItem = getEntryTiming(item); console.log(curItem); } }); } </script> </body> </html>
然後如上會有2個console.log 列印資料,我們到控制檯中看到列印資訊如下:
如上圖所示,我們可以看到通過 getEntryTiming 方法計算後,會拿到各對應的值。
4.2 performance.now()
該方法會返回一個當前頁面執行的時間的時間戳,可以用來精確計算程式執行的實際。
比如如下,我迴圈100萬次,然後返回一個陣列,我們來看下程式碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <script type="text/javascript"> function doFunc() { var arrs = []; for (var i = 0; i < 1000000; i++) { arrs.push({ 'label': i, 'value': i }); } return arrs; } var t1 = window.performance.now(); console.log(t1); doFunc(); var t2 = window.performance.now(); console.log(t2); console.log('doFunc函式執行的時間為:'+ (t2 - t1) + '毫秒'); </script> </body> </html>
然後我們再來列印下 doFunc() 這個函式執行了多久,如下所示:
我們也知道我們還有一個時間就是 Date.now(), 但是performance.now()與Date.now()方法不同的是:
該方法使用了一個浮點數,返回的是以毫秒為單位,小數點精確到微妙級別的時間。相對於Date.now() 更精確,並且不會受系統程式堵塞的影響。
下面我們來看看使用 Date.now()方法使用的demo如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <script type="text/javascript"> function doFunc() { var arrs = []; for (var i = 0; i < 1000000; i++) { arrs.push({ 'label': i, 'value': i }); } return arrs; } var t1 = Date.now(); console.log(t1); doFunc(); var t2 = Date.now(); console.log(t2); console.log('doFunc函式執行的時間為:'+ (t2 - t1) + '毫秒'); </script> </body> </html>
執行的結果如下:
注意:performance.timing.navigationStart + performance.now() 約等於Date.now();
4.3 performance.mark()
該方法的含義是用來自定義新增標記的時間, 方便我們計算程式的執行耗時。該方法使用如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <script type="text/javascript"> function doFunc() { var arrs = []; for (var i = 0; i < 1000000; i++) { arrs.push({ 'label': i, 'value': i }); } return arrs; } // 函式執行前做個標記 var mStart = 'mStart'; var mEnd = 'mEnd'; window.performance.mark(mStart); doFunc(); // 函式執行之後再做個標記 window.performance.mark(mEnd); // 然後測量這兩個標記之間的距離,並且儲存起來 var name = 'myMeasure'; window.performance.measure(name, mStart, mEnd); // 下面我們通過 performance.getEntriesByName 方法來獲取該值 console.log(performance.getEntriesByName('myMeasure')); console.log(performance.getEntriesByType('measure')); </script> </body> </html>
如上程式碼,我們通過 window.performance.measure(name, mStart, mEnd); 這個方法做出標記後,我們可以使用performance.getEntriesByName('myMeasure') 和 performance.getEntriesByType('measure') 獲取該值。
如下圖所示:
4.4 performance.getEntriesByType()
該方法返回一個 PerformanceEntry 物件的列表,基於給定的 entry type, 如上程式碼 performance.getEntriesByType('measure') 就可以獲取該值。
4.5 performance.clearMeasures()
從瀏覽器的效能輸入緩衝區中移除自定義新增的 measure. 程式碼如下所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <script type="text/javascript"> function doFunc() { var arrs = []; for (var i = 0; i < 1000000; i++) { arrs.push({ 'label': i, 'value': i }); } return arrs; } // 函式執行前做個標記 var mStart = 'mStart'; var mEnd = 'mEnd'; window.performance.mark(mStart); doFunc(); // 函式執行之後再做個標記 window.performance.mark(mEnd); // 然後測量這兩個標記之間的距離,並且儲存起來 var name = 'myMeasure'; window.performance.measure(name, mStart, mEnd); // 下面我們通過 performance.getEntriesByName 方法來獲取該值 console.log(performance.getEntriesByName('myMeasure')); console.log(performance.getEntriesByType('measure')); // 使用 performance.clearMeasures() 方法來清除 自定義新增的 measure performance.clearMeasures(); console.log(performance.getEntriesByType('measure')); </script> </body> </html>
如上我們在最後程式碼中使用 performance.clearMeasures() 方法清除了所有自定義的measure。然後我們後面重新使用 console.log(performance.getEntriesByType('measure'));列印下,看到如下資訊:
4.6 performance.getEntriesByName(name屬性的值)
該方法返回一個 PerformanceEntry 物件的列表,基於給定的 name 和 entry type。
4.7 performance.toJSON()
該方法是一個 JSON 格式轉化器,返回 Performance 物件的 JSON 物件。如下程式碼所示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <script type="text/javascript"> console.log(window.performance); var js = window.performance.toJSON(); console.log("json = " + JSON.stringify(js)); </script> </body> </html>
然後列印資訊如下:
五:使用performane編寫小工具
首先html使用程式碼如下(切記一定要把初始程式碼放到window.onload裡面,因為確保圖片載入完成):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>performance演示</title> </head> <body> <h1>計算時間</h1> <img src="http://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg" /> <img src="https://aecpm.alicdn.com/simba/img/TB1XotJXQfb_uJkSnhJSuvdDVXa.jpg" /> <script type="text/javascript" src="./js/performance.js"></script> <script type="text/javascript"> window.onload = function() { window.performanceTool.getPerformanceTiming(); }; </script> </body> </html>
然後頁面進行預覽效果如下:
如上圖我們就可以很清晰的可以看到,頁面的基本資訊了,比如:重定向耗時、Appcache耗時、DNS查詢耗時、TCP連結耗時、HTTP請求耗時、請求完畢到DOM載入耗時、解析DOM樹耗時、白屏時間耗時、load事件耗時、及 頁面載入完成的時間。頁面載入完成的時間 是上面所有時間的總和。及下面,我們也可以分別清晰的看到,js、css、image、video、等資訊的資源載入及總共用了多少時間。
js基本程式碼如下:
src/utils.js 程式碼如下:
export function isObject(obj) { return obj !== null && (typeof obj === 'object') } // 格式化成毫秒 export function formatMs(time) { if (typeof time !== 'number') { console.log('時間必須為數字'); return; } // 毫秒轉換成秒 返回 if (time > 1000) { return (time / 1000).toFixed(2) + 's'; } // 預設返回毫秒 return Math.round(time) + 'ms'; } export function isImg(param){ if (/\.(gif|jpg|jpeg|png|webp|svg)/i.test(param)) { return true; } return false; } export function isJS(param){ if (/\.(js)/i.test(param)) { return true; } return false; } export function isCss(param){ if (/\.(css)/i.test(param)) { return true; } return false; } export function isVideo(param){ if (/\.(mp4|rm|rmvb|mkv|avi|flv|ogv|webm)/i.test(name)) { return true; } return false; } export function checkResourceType(param){ if (isImg(param)) { return 'image'; } if (isJS(param)) { return 'javascript'; } if (isCss(param)) { return 'css'; } if (isVideo(param)) { return 'video'; } return 'other' }
js/index.js 程式碼如下:
var utils = require('./utils'); var formatMs = utils.formatMs; var isObject = utils.isObject; var checkResourceType = utils.checkResourceType; function Performance() {}; Performance.prototype = { // 獲取資料資訊 getPerformanceTiming: function() { // 初始化資料 this.init(); if (!isObject(this.timing)) { console.log('值需要是一個物件型別'); return; } // 過早獲取 loadEventEnd值會是0 var loadTime = this.timing.loadEventEnd - this.timing.navigationStart; if (loadTime < 0) { setTimeout(() => { this.getPerformanceTiming(); }, 200); return; } // 獲取解析後的資料 this.afterDatas.timingFormat = this._setTiming(loadTime); this.afterDatas.enteriesResouceDataFormat = this._setEnteries(); this._show(); }, init: function() { this.timing = window.performance.timing; // 獲取資源型別為 resource的所有資料 this.enteriesResouceData = window.performance.getEntriesByType('resource'); }, // 儲存原始資料 timing: {}, // 原始enteries資料 enteriesResouceData: [], // 儲存解析後的資料 afterDatas: { timingFormat: {}, enteriesResouceDataFormat: {}, enteriesResouceDataTiming: { "js": 0, "css": 0, "image": 0, "video": 0, "others": 0 } }, _setTiming: function(loadTime) { var timing = this.timing; // 對資料進行計算 var data = { "重定向耗時": formatMs(timing.redirectEnd - timing.redirectStart), "Appcache耗時": formatMs(timing.domainLookupStart - timing.fetchStart), "DNS查詢耗時": formatMs(timing.domainLookupEnd - timing.domainLookupStart), "TCP連結耗時": formatMs(timing.connectEnd - timing.connectStart), "HTTP請求耗時": formatMs(timing.responseEnd - timing.responseStart), "請求完畢到DOM載入耗時": formatMs(timing.domInteractive - timing.responseEnd), "解析DOM樹耗時": formatMs(timing.domComplete - timing.domInteractive), "白屏時間耗時": formatMs(timing.responseStart - timing.navigationStart), "load事件耗時": formatMs(timing.loadEventEnd - timing.loadEventStart), "頁面載入完成的時間": formatMs(loadTime) }; return data; }, _setEnteries: function() { var enteriesResouceData = this.enteriesResouceData; var imageArrs = [], jsArrs = [], cssArrs = [], videoArrs = [], otherArrs = []; enteriesResouceData.map(item => { var d = { '資源名稱': item.name, 'HTTP協議型別' : item.nextHopProtocol, "TCP連結耗時" : formatMs(item.connectEnd - item.connectStart), "載入時間" : formatMs(item.duration) }; switch(checkResourceType(item.name)) { case 'image': this.afterDatas.enteriesResouceDataTiming.image += item.duration; imageArrs.push(d); break; case 'javascript': this.afterDatas.enteriesResouceDataTiming.js += item.duration; jsArrs.push(d); break; case 'css': this.afterDatas.enteriesResouceDataTiming.css += item.duration; cssArrs.push(d); break; case 'video': this.afterDatas.enteriesResouceDataTiming.video += item.duration; videoArrs.push(d); break; case 'others': this.afterDatas.enteriesResouceDataTiming.others += item.duration; otherArrs.push(d); break; } }); return { 'js': jsArrs, 'css': cssArrs, 'image': imageArrs, 'video': videoArrs, 'others': otherArrs } }, _show: function() { console.table(this.afterDatas.timingFormat); for( var key in this.afterDatas.enteriesResouceDataFormat ){ console.group(key + "--- 共載入時間" + formatMs(this.afterDatas.enteriesResouceDataTiming[key])); console.table(this.afterDatas.enteriesResouceDataFormat[key]); console.groupEnd(key); } } }; var Per = new Performance(); module.exports = Per;
注意:把github原始碼下載完成後,需要 執行 npm run build 打包,打包完成後,把dist/下的js檔案複製到自己專案中即可,然後在專案中引入該js檔案,然後呼叫即可在控制檯中看到一些資訊效果。
注:基本程式碼也參考了github上一些程式碼。這些不重要,重要的是學到東西,並且在專案中能用起來。提高效率。