Performance --- 前端效能監控

龍恩0707發表於2019-06-05

閱讀目錄

一:什麼是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 原始碼檢視

注意:把github原始碼下載完成後,需要 執行 npm run build 打包,打包完成後,把dist/下的js檔案複製到自己專案中即可,然後在專案中引入該js檔案,然後呼叫即可在控制檯中看到一些資訊效果。

注:基本程式碼也參考了github上一些程式碼。這些不重要,重要的是學到東西,並且在專案中能用起來。提高效率。

相關文章