前端質量監控之頁面性能相關

前言

最近有幸參與一個前端質量監控類項目的重構,算是個人初次接觸到前端質量監控相關的知識,對於其中的性能統計部分很感興趣,查詢資料之後寫了文章,作爲個人學習記錄,如有錯誤,敬請斧正

1.頁面整體性能

通過瀏覽器提供的 window.performance.timing 方法,我們能夠得到網頁每個處理階段的精確時間。打開一個頁面後,這些信息會被瀏覽器記錄下來,我們直接在控制檯輸出,就可以查看結果

clipboard.png

該對象下有多個字段,每個字段都對應着瀏覽器加載一個頁面的某個階段,下面這張圖詳細的展示了瀏覽器加載一個頁面的詳細過程

https://segmentfault.com/img/...
clipboard.png

    window.performance = {
        timing: {
            // 同一個域下,前一個網 unload 的時間戳,
            // 如果直接複製地址進入或者不是同域
            // 則與 fetchStart 值相等
            navigationStart: 1441112691935,
     
            // 前一個網頁unload 的時間戳,
            // 如果無前一個網頁 unload
            // 或者前一個網頁與當前頁面不同域,則值爲 0
            unloadEventStart: 0,
     
            // 和 unloadEventStart 相對應
            // 返回前一個網頁 unload  事件綁定的回調函數執行完畢的時間戳
            unloadEventEnd: 0,
     
            // 第一個 HTTP 重定向發生時的時間。
            // 有跳轉且是同域名內的重定向纔算
            // 否則值爲 0 
            redirectStart: 0,
     
            // 最後一個 HTTP 重定向完成時的時間
            // 有跳轉且是同域名內部的重定向纔算
            // 否則值爲 0 
            redirectEnd: 0,
     
            // 瀏覽器準備好使用 HTTP 請求抓取文檔的時間
            // 發生在檢查本地緩存之前
            fetchStart: 1441112692155,
     
            // DNS 域名查詢開始的時間
            // 如果使用了本地緩存(即無 DNS 查詢)或持久連接
            // 則與 fetchStart 值相等
            domainLookupStart: 1441112692155,
     
            // DNS 域名查詢完成的時間
            // 如果使用了本地緩存(即無 DNS 查詢)或持久連接
            // 則與 fetchStart 值相等
            domainLookupEnd: 1441112692155,
     
            // HTTP(TCP) 開始建立連接的時間
            // 如果是持久連接,則與 fetchStart 值相等
            // 注意如果在傳輸層發生了錯誤且重新建立連接,
            // 則顯示的是新建立的連接開始的時間
            connectStart: 1441112692155,
     
            // HTTP(TCP) 完成建立連接的時間(完成握手)
            // 如果是持久連接,則與 fetchStart 值相等
            // 注意如果在傳輸層發生了錯誤且重新建立連接,
            // 則顯示的是新建立的連接完成的時間
            // 注意這裏握手結束,包括安全連接建立完成、SOCKS 授權通過
            connectEnd: 1441112692155,
     
            // HTTPS 連接開始的時間,
            // 如果不是安全連接,則值爲 0
            secureConnectionStart: 0,
     
            // HTTP 請求讀取真實文檔開始的時間(完成建立連接
            // 包括從本地讀取緩存
            // 連接錯誤重連時,這裏顯示的也是新建立連接的時間
            requestStart: 1441112692158,
     
            // HTTP 開始接收響應的時間(獲取到第一個字節)
            // 包括從本地讀取緩存
            responseStart: 1441112692686,
     
            // HTTP 響應全部接收完成的時間(獲取到最後一個字節)
            // 包括從本地讀取緩存
            responseEnd: 1441112692687,
     
            // 開始解析渲染 DOM 樹的時間
            // 此時 Document.readyState 變爲 loading
            // 並將拋出 readystatechange 相關事件
            domLoading: 1441112692690,
     
            // 完成解析 DOM 樹的時間
            // Document.readyState 變爲 interactive
            // 並將拋出 readystatechange 相關事件
            // 注意只是 DOM 樹解析完成
            // 此時並沒有開始加載網頁內的資源
            domInteractive: 1441112693093,
     
            // DOM 解析完成後,網頁內資源加載開始的時間
            // 在 DOMContentLoaded 事件拋出前發生
            domContentLoadedEventStart: 1441112693093,
     
            // DOM 解析完成後,
            // 網頁內資源加載完成的時間
            // 如 JS 腳本加載執行完
            domContentLoadedEventEnd: 1441112693101,
     
            // DOM 樹解析完成
            // 且資源也準備就緒的時間
            // Document.readyState 變爲 complete
            // 此時拋出 readystatechange 相關事件
            domComplete: 1441112693214,
     
            // load 事件發送給文檔
            // 也即 load 回調函數開始執行的時間
            // 注意如果沒有綁定 load 事件,值爲 0
            loadEventStart: 1441112693214,
     
            // load 事件的回調函數執行完畢的時間
            loadEventEnd: 1441112693215
        }
    }

通過這些值,我們就能得到某個階段具體的時間差,進行一些簡單的計算, 就能夠得到網頁的各項性能數據,便於我們在某個階段做優化

    
    // DOM解析時間
    var domParseTime = domComplete - responseEnd;
    
    // DNS解析時間
    var domainLookUpTime = domainLookupEnd - domainLookupStart;

2. 頁面中各個資源的性能

上面我們用 window.performance.timing 方法得到的是整個頁面的耗時,在一些情況下,我們想要知道頁面上某個靜態資源的加載時間,那麼我們就可以用三個方法來獲取這些信息

  • window.performance.getEntries // 返回網頁中所有資源和標記的數據
  • window.performance.getEntriesByType // 根據entryType返回數據
  • window.performance.getEntriesByName // 根據name返回數據

其中 window.performance.getEntries 可以獲取到所有資源信息和所有標記信息(何爲標記信息,我們後面會講到), 而我們想看的只是 entryType: "resource" 這些資源的信息,所以我們可以直接使用 window.performance.getEntriesByType('resource') 方法來獲得我們的信息,該方法會返回一個數組,包含字段如下

    var entries = [{  
        // 資源的絕對路徑
        name: "http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js",
        // 資源類型
        entryType: "resource",
        // css: 通過css請求的資源。類型不定
        // img: 圖片
        // link: 通過link標籤請求的資源: css/favicon
        // script: js文件
        // xmlhttprequest: 一般爲GET方式的數據接口
        initiatorType: "script", 
        // 加載時間
        duration: 18.13399999809917,
     
        // 這些字段的意義同上面
        // 不同之處只表示這一個資源的時間
        redirectStart: 0,
        redirectEnd: 0,
        fetchStart: 233.346999992829828,
        domainLookupStart: 0,
        domainLookupEnd: 0,
        connectStart: 0,
        connectEnd: 0,
        secureConnectionStart: 0,
        requestStart: 0,
        responseStart: 0,
        responseEnd: 442.7109999960521,
        startTime: 424.57699999795295
  }];

這些數據裏面,initiatorType的值需要注意下, initiatorType 並不是指資源的類型,而是指 請求該資源的請求類型, 如上圖所示 initiatorType: 'css' 的時候,資源文件並不是一個css文件,而是一張 img圖片,表明該圖片是通過css去請求到的(通常是設置背景圖),而直接在網頁中通過 <img /> 或在代碼中通過 new Image 請求的圖片,initiatorType 值都爲 img,我們以下面這段代碼爲例

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <link rel="stylesheet" href="index.css">
            <link rel="stylesheet" href="http://apps.bdimg.com/libs/bootstrap/3.3.4/css/bootstrap.css">
            <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
            <style>
                body > div {
                    height: 300px;
                    width: 300px;
                    margin: 10px;
                    float: left;
                }
    
                .css-img div {
                    background: url('http://temp.im/110x110/FF2D55/000') no-repeat;
                }
            </style>
        </head>
        <body>
            <div class="css-img">
                <p>通過CSS加載的圖片</p>
                <div></div>
            </div>
    
            <div class="normal-img">
                <p>通過 img 標籤直接加載</p>
                <img src="http://temp.im/178x178/007AFF/fff" />
            </div>
    
            <div class="new-img">
                <p>通過 new Image() 加載的圖片</p>
    
            </div>
        </body>
        <script>
    
            window.onload = function () {
                var tempImage = new Image();
                tempImage.src = "http://temp.im/288x288/FF9500/000";
                document.querySelector('.new-img').appendChild(tempImage)
            }
    
      </script>
    </html>

clipboard.png

打開頁面之後在控制檯輸出結果, 也驗證了結果

clipboard.png

另外值得注意的一個點是,除了這三張圖片,我們在頁面中還加載了2個css文件和1個js文件,分別是

  • index.css // 本地文件
  • bootstrap.min.css // 遠程
  • jquery.min.js // 遠程

仔細觀察的話,會發現這幾個文件有一些不同之處,我們對數據稍作處理,方便參看

我們會發現,其中的 bootstrap.min.css 的很多字段數據都爲0,查詢資料之後才知道,該方法通常情況下只能獲得本域名下資源的加載信息(所以 index.css 的信息直接就能獲取到)

如果想要獲取遠程資源的加載信息的只有在response裏面顯式的設置 Timing-Allow-Origin:* 類似於(Acces-Control-Allow-Origin的設置)

我們直接打開了 jquery.min.js 連接, 看到了 Timing-Allow-Origin 字段,所以我們能夠獲得該資源的準確信息, 而在 bootstrap.min.css 的返回中,我們沒有找到 Timing-Allow-Origin 字段, 所以數據都顯示爲0

clipboard.png


3. 代碼執行計時

通常情況下,我們想要取得一段代碼的執行時間,會做以下操作

      var start = +new Date();
      for (var i = 0; i < 100; i++) {
          ret.push(i);
      }
      var end = +new Date();
      console.log('執行時間', end - start);

但不足之處在於, new Date 的精確度只到秒, 而有時候我們的代碼執行在1s之內,計算出的差值爲0,此時就顯得無能爲力。所以,我們可能通過如下兩種方式,解決這個問題

1. 通過 window.performance.now

    var start = window.performance.now();
    for (var i = 0; i < 100; i++) {
        ret.push(i);
    }
    var end = window.performance.now();
    console.log('執行時間', end - start);

使用方式十分簡單,該方法能夠計算出精確到 百萬分之一秒 的時間差。與 new Date 不同之處在於 window.performance.now 方法返回的是相對於 performance.timing.navigationStart 即頁面初始化的時間,但我們的關注點是差值, 所以時間從哪兒算起,對於我們來說並不重要

2. 使用 window.performance.markwindow.performance.measure

    // 標記開始
    window.performance.mark("start");
    for (var i = 0; i < 100; i++) {
        ret.push(i);
    }
    // 標記結束
    window.performance.mark("end");
    // 計算差值並命名爲difference, 無返回
    window.performance.measure("difference", "start", "end");

通過 window.performance.mark 方法,我們在指定地方打上一個記號,此時不會返回任何值,會被記錄到performance 對象裏, entryType 被默認標記爲了 mark

然後通過 window.performance.measure 計算出差值,並通過第一個參數對其賦名爲 difference, 此時也沒有返回值,這些值也被記錄到了 performance 對象裏,它們的 entryType 被默認標記爲了 measure.

clipboard.png

所以我們可以通過兩種方式來查看差值

  • window.performance.getEntriesByType('measure')
  • `window.perform

clipboard.png
esByName('difference')`

![圖片上傳中...]

在使用完了之後,我們還可以對這些標識,進行統一的清理

    // 清除指定標記點
    window.performance.clearMarks('start');  
    // 清除所有標記點
    window.performance.clearMarks();
 
    // 清除指定差值數據
    window.performance.clearMeasures('difference');  
    // 清除所有差值數據
    window.performance.clearMeasures();  

通過這兩種方式,都可以精確的計算出十分精確的時間差值,那如何做選擇呢? 我的建議是,如果只是單純的想要得到少數的差值,直接在代碼中使用 now, 而如果需要統計大量的值, 就應該使用 markmeasure 方法,因爲這些值都會存在 performance 對象裏,我們可以在任意需要的時候,通過讀取數組,十分便捷的對數據做統一處理與管理

API支持 && 參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章