前言
最近有幸參與一個前端質量監控類項目的重構,算是個人初次接觸到前端質量監控相關的知識,對於其中的性能統計部分很感興趣,查詢資料之後寫了文章,作爲個人學習記錄,如有錯誤,敬請斧正
1.頁面整體性能
通過瀏覽器提供的 window.performance.timing
方法,我們能夠得到網頁每個處理階段的精確時間。打開一個頁面後,這些信息會被瀏覽器記錄下來,我們直接在控制檯輸出,就可以查看結果
該對象下有多個字段,每個字段都對應着瀏覽器加載一個頁面的某個階段,下面這張圖詳細的展示了瀏覽器加載一個頁面的詳細過程
https://segmentfault.com/img/...
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>
打開頁面之後在控制檯輸出結果, 也驗證了結果
另外值得注意的一個點是,除了這三張圖片,我們在頁面中還加載了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
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.mark
和 window.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
.
所以我們可以通過兩種方式來查看差值
window.performance.getEntriesByType('measure')
- `window.perform
esByName('difference')`
![圖片上傳中...]
在使用完了之後,我們還可以對這些標識,進行統一的清理
// 清除指定標記點
window.performance.clearMarks('start');
// 清除所有標記點
window.performance.clearMarks();
// 清除指定差值數據
window.performance.clearMeasures('difference');
// 清除所有差值數據
window.performance.clearMeasures();
通過這兩種方式,都可以精確的計算出十分精確的時間差值,那如何做選擇呢? 我的建議是,如果只是單純的想要得到少數的差值,直接在代碼中使用 now
, 而如果需要統計大量的值, 就應該使用 mark
和 measure
方法,因爲這些值都會存在 performance
對象裏,我們可以在任意需要的時候,通過讀取數組,十分便捷的對數據做統一處理與管理