使用 window.performance 提供了一組精確的數據,通過簡單的計算就能得出一些網頁性能數據。css
配合上報一些客戶端瀏覽器的設備類型等數據,就能夠實現簡單的統計啦!segmentfault
額,先看下兼容性如何:http://caniuse.com/#feat=nav-timing數組
這篇文章中 Demo 的運行環境爲最新的 Chrome 的控制檯,若是你用的是其餘瀏覽器,自查兼容性哈~瀏覽器
先來看看在 Chrome 瀏覽器控制檯中執行 window.performance
會出現什麼:緩存
先看下一個請求發出的整個過程當中,各類環節的時間順序:安全
// 獲取 performance 數據 var performance = { // memory 是非標準屬性,只在 Chrome 有 // 財富問題:我有多少內存 memory: { usedJSHeapSize: 16100000, // JS 對象(包括V8引擎內部對象)佔用的內存,必定小於 totalJSHeapSize totalJSHeapSize: 35100000, // 可以使用的內存 jsHeapSizeLimit: 793000000 // 內存大小限制 }, // 哲學問題:我從哪裏來? navigation: { redirectCount: 0, // 若是有重定向的話,頁面經過幾回重定向跳轉而來 type: 0 // 0 即 TYPE_NAVIGATENEXT 正常進入的頁面(非刷新、非重定向等) // 1 即 TYPE_RELOAD 經過 window.location.reload() 刷新的頁面 // 2 即 TYPE_BACK_FORWARD 經過瀏覽器的前進後退按鈕進入的頁面(歷史記錄) // 255 即 TYPE_UNDEFINED 非以上方式進入的頁面 }, timing: { // 在同一個瀏覽器上下文中,前一個網頁(與當前頁面不必定同域)unload 的時間戳,若是無前一個網頁 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 // 字母順序 // connectEnd: 1441112692155, // connectStart: 1441112692155, // domComplete: 1441112693214, // domContentLoadedEventEnd: 1441112693101, // domContentLoadedEventStart: 1441112693093, // domInteractive: 1441112693093, // domLoading: 1441112692690, // domainLookupEnd: 1441112692155, // domainLookupStart: 1441112692155, // fetchStart: 1441112692155, // loadEventEnd: 1441112693215, // loadEventStart: 1441112693214, // navigationStart: 1441112691935, // redirectEnd: 0, // redirectStart: 0, // requestStart: 1441112692158, // responseEnd: 1441112692687, // responseStart: 1441112692686, // secureConnectionStart: 0, // unloadEventEnd: 0, // unloadEventStart: 0 } };
具體的含義都在註釋裏說明了,接下來咱們看下能用這些數據作什麼?服務器
在註釋中,我用【重要】標註了我我的認爲比較有用的數據,用【緣由】標註了爲啥要重點關注這個數據網絡
// 計算加載時間 function getPerformanceTiming () { var performance = window.performance; if (!performance) { // 當前瀏覽器不支持 console.log('你的瀏覽器不支持 performance 接口'); return; } var t = performance.timing; var times = {}; //【重要】頁面加載完成的時間 //【緣由】這幾乎表明了用戶等待頁面可用的時間 times.loadPage = t.loadEventEnd - t.navigationStart; //【重要】解析 DOM 樹結構的時間 //【緣由】檢討下你的 DOM 樹嵌套是否是太多了! times.domReady = t.domComplete - t.responseEnd; //【重要】重定向的時間 //【緣由】拒絕重定向!好比,http://example.com/ 就不應寫成 http://example.com times.redirect = t.redirectEnd - t.redirectStart; //【重要】DNS 查詢時間 //【緣由】DNS 預加載作了麼?頁面內是否是使用了太多不一樣的域名致使域名查詢的時間太長? // 可以使用 HTML5 Prefetch 預查詢 DNS ,見:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364) times.lookupDomain = t.domainLookupEnd - t.domainLookupStart; //【重要】讀取頁面第一個字節的時間 //【緣由】這能夠理解爲用戶拿到你的資源佔用的時間,加異地機房了麼,加CDN 處理了麼?加帶寬了麼?加 CPU 運算速度了麼? // TTFB 即 Time To First Byte 的意思 // 維基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte times.ttfb = t.responseStart - t.navigationStart; //【重要】內容加載完成的時間 //【緣由】頁面內容通過 gzip 壓縮了麼,靜態資源 css/js 等壓縮了麼? times.request = t.responseEnd - t.requestStart; //【重要】執行 onload 回調函數的時間 //【緣由】是否太多沒必要要的操做都放到 onload 回調函數裏執行了,考慮過延遲加載、按需加載的策略麼? times.loadEvent = t.loadEventEnd - t.loadEventStart; // DNS 緩存時間 times.appcache = t.domainLookupStart - t.fetchStart; // 卸載頁面的時間 times.unloadEvent = t.unloadEventEnd - t.unloadEventStart; // TCP 創建鏈接完成握手的時間 times.connect = t.connectEnd - t.connectStart; return times; }
這個函數返回的將是一個數組,包含了頁面中全部的 HTTP 請求,這裏拿第一個請求 window.performance.getEntries()[0]
舉例。
注意 HTTP 請求有可能命中本地緩存,因此請求響應的間隔將很是短
能夠看到,與 performance.timing 對比:
沒有與 DOM 相關的屬性:app
navigationStartdom
unloadEventStart
unloadEventEnd
domLoading
domInteractive
domContentLoadedEventStart
domContentLoadedEventEnd
domComplete
loadEventStart
loadEventEnd
新增屬性:
name
entryType
initiatorType
duration
與 window.performance.timing 中包含的屬性就再也不介紹了:
var entry = { // 資源名稱,也是資源的絕對路徑 name: "http://cdn.alloyteam.com/wp-content/themes/alloyteam/style.css", // 資源類型 entryType: "resource", // 誰發起的請求 initiatorType: "link", // link 即 <link> 標籤 // script 即 <script> // redirect 即重定向 // 加載時間 duration: 18.13399999809917, redirectStart: 0, redirectEnd: 0, fetchStart: 424.57699999795295, domainLookupStart: 0, domainLookupEnd: 0, connectStart: 0, connectEnd: 0, secureConnectionStart: 0, requestStart: 0, responseStart: 0, responseEnd: 442.7109999960521, startTime: 424.57699999795295 };
能夠像 getPerformanceTiming
獲取網頁的時間同樣,獲取某個資源的時間:
// 計算加載時間 function getEntryTiming (entry) { var t = entry; var times = {}; // 重定向的時間 times.redirect = t.redirectEnd - t.redirectStart; // DNS 查詢時間 times.lookupDomain = t.domainLookupEnd - t.domainLookupStart; // 內容加載完成的時間 times.request = t.responseEnd - t.requestStart; // TCP 創建鏈接完成握手的時間 times.connect = t.connectEnd - t.connectStart; // 掛載 entry 返回 times.name = entry.name; times.entryType = entry.entryType; times.initiatorType = entry.initiatorType; times.duration = entry.duration; return times; } // test // var entries = window.performance.getEntries(); // entries.forEach(function (entry) { // var times = getEntryTiming(entry); // console.log(times); // });
performance.now()
與 Date.now()
不一樣的是,返回了以微秒(百萬分之一秒)爲單位的時間,更加精準。
而且與 Date.now()
會受系統程序執行阻塞的影響不一樣,performance.now()
的時間是以恆定速率遞增的,不受系統時間的影響(系統時間可被人爲或軟件調整)。
注意 Date.now()
輸出的是 UNIX 時間,即距離 1970 的時間,而 performance.now()
輸出的是相對於 performance.timing.navigationStart
(頁面初始化) 的時間。
使用 Date.now()
的差值並不是絕對精確,由於計算時間時受系統限制(可能阻塞)。但使用 performance.now()
的差值,並不影響咱們計算程序執行的精確時間。
// 計算程序執行的精確時間 function getFunctionTimeWithDate (func) { var timeStart = Data.now(); // 執行開始 func(); // 執行結束 var timeEnd = Data.now(); // 返回執行時間 return (timeEnd - timeStart); } function getFunctionTimeWithPerformance (func) { var timeStart = window.performance.now(); // 執行開始 func(); // 執行結束 var timeEnd = window.performance.now(); // 返回執行時間 return (timeEnd - timeStart); }
使用 performance.mark()
標記各類時間戳(就像在地圖上打點),保存爲各類測量值(測量地圖上的點之間的距離),即可以批量地分析這些數據了。
直接上示例代碼看註釋便明白:
function randomFunc (n) { if (!n) { // 生成一個隨機數 n = ~~(Math.random() * 10000); } var nameStart = 'markStart' + n; var nameEnd = 'markEnd' + n; // 函數執行前作個標記 window.performance.mark(nameStart); for (var i = 0; i < n; i++) { // do nothing } // 函數執行後再作個標記 window.performance.mark(nameEnd); // 而後測量這個兩個標記間的時間距離,並保存起來 var name = 'measureRandomFunc' + n; window.performance.measure(name, nameStart, nameEnd); } // 執行三次看看 randomFunc(); randomFunc(); // 指定一個名字 randomFunc(888);
// 看下保存起來的標記 mark var marks = window.performance.getEntriesByType('mark'); console.log(marks);
// 看下保存起來的測量 measure var measure = window.performance.getEntriesByType('measure'); console.log(measure);
// 看下咱們自定義的測量 var entries = window.performance.getEntriesByName('measureRandomFunc888'); console.log(entries);
能夠看到,for
循環 measureRandomFunc888
的時候
結束時間爲: 4875.1199999969685
開始時間爲:4875.112999987323
執行時間爲:4875.1199999969685 - 4875.112999987323 = 0.00700000964
標記和測量用完了能夠清除掉:
// 清除指定標記 window.performance.clearMarks('markStart888'); // 清除全部標記 window.performance.clearMarks(); // 清除指定測量 window.performance.clearMeasures('measureRandomFunc'); // 清除全部測量 window.performance.clearMeasures();
固然 performance.mark()
只是提供了一些簡便的測量方式,好比以前咱們測量 domReady 是這麼測的:
// 計算 domReady 時間 var t = performance.timing var domReadyTime = t.domComplete - t.responseEnd; console.log(domReadyTime)
其實就能夠寫成:
window.performance.measure('domReady','responseEnd' , 'domComplete'); var domReadyMeasure = window.performance.getEntriesByName('domReady'); console.log(domReadyMeasure);
熟悉 Chrome 開發者工具的朋友應該知道:在開發環境下,其實咱們本身打開 Chrome 的開發者工具,切換到網絡面板,就能很詳細的看到網頁性能相關的數據。但當咱們須要統計分析用戶打開咱們網頁時的性能如什麼時候,咱們將 performance 原始信息或經過簡單計算後的信息(如上面寫到的 getPerformanceTiming()
和 getEntryTiming()
) 上傳到服務器,配合其餘信息(如 HTTP 請求頭信息),就完美啦~
(完)感謝閱讀!
原文連接