使用 window.performance 提供了一組精確的資料,經過簡單的計算就能得出一些網頁效能資料。
配合上報一些客戶端瀏覽器的裝置型別等資料,就可以實現簡單的統計啦!
額,先看下相容性如何:http://caniuse.com/#feat=nav-timing
這篇文章中 Demo 的執行環境為最新的 Chrome 的控制檯,如果你用的是其他瀏覽器,自查相容性哈~
先來看看在 Chrome 瀏覽器控制檯中執行 window.performance 會出現什麼:
簡單解釋下 performance 中的屬性:
先看下一個請求發出的整個過程中,各種環節的時間順序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
// 獲取 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 } }; |
具體的含義都在註釋裡說明了,接下來我們看下能用這些資料做什麼?
使用 performance.timing 資訊簡單計算出網頁效能資料
在註釋中,我用【重要】標註了我個人認為比較有用的資料,用【原因】標註了為啥要重點關注這個資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// 計算載入時間 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; } |
使用performance.getEntries() 獲取所有資源請求的時間資料
這個函式返回的將是一個陣列,包含了頁面中所有的 HTTP 請求,這裡拿第一個請求 window.performance.getEntries()[0] 舉例。 注意 HTTP 請求有可能命中本地快取,所以請求響應的間隔將非常短 可以看到,與 performance.timing 對比: 沒有與 DOM 相關的屬性:
- navigationStart
- unloadEventStart
- unloadEventEnd
- domLoading
- domInteractive
- domContentLoadedEventStart
- domContentLoadedEventEnd
- domComplete
- loadEventStart
- loadEventEnd
新增屬性:
- name
- entryType
- initiatorType
- duration
與 window.performance.timing 中包含的屬性就不再介紹了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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 獲取網頁的時間一樣,獲取某個資源的時間:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// 計算載入時間 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() 精確計算程式執行時間
performance.now() 與 Date.now() 不同的是,返回了以微秒(百萬分之一秒)為單位的時間,更加精準。
並且與 Date.now() 會受系統程式執行阻塞的影響不同,performance.now() 的時間是以恆定速率遞增的,不受系統時間的影響(系統時間可被人為或軟體調整)。
注意 Date.now() 輸出的是 UNIX 時間,即距離 1970 的時間,而 performance.now() 輸出的是相對於 performance.timing.navigationStart(頁面初始化) 的時間。
使用 Date.now() 的差值並非絕對精確,因為計算時間時受系統限制(可能阻塞)。但使用 performance.now() 的差值,並不影響我們計算程式執行的精確時間。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 計算程式執行的精確時間 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() 也可以精確計算程式執行時間
使用 performance.mark() 標記各種時間戳(就像在地圖上打點),儲存為各種測量值(測量地圖上的點之間的距離),便可以批量地分析這些資料了。
直接上示例程式碼看註釋便明白:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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); |
1 2 3 |
// 看下儲存起來的標記 mark var marks = window.performance.getEntriesByType('mark'); console.log(marks); |
1 2 3 |
// 看下儲存起來的測量 measure var measure = window.performance.getEntriesByType('measure'); console.log(measure); |
1 2 3 |
// 看下我們自定義的測量 var entries = window.performance.getEntriesByName('measureRandomFunc888'); console.log(entries); |
可以看到,for 迴圈 measureRandomFunc888 的時候
結束時間為: 4875.1199999969685
開始時間為:4875.112999987323
執行時間為:4875.1199999969685 – 4875.112999987323 = 0.00700000964
標記和測量用完了可以清除掉:
1 2 3 4 5 6 7 8 9 |
// 清除指定標記 window.performance.clearMarks('markStart888'); // 清除所有標記 window.performance.clearMarks(); // 清除指定測量 window.performance.clearMeasures('measureRandomFunc'); // 清除所有測量 window.performance.clearMeasures(); |
當然 performance.mark() 只是提供了一些簡便的測量方式,比如之前我們測量 domReady 是這麼測的:
1 2 3 4 |
// 計算 domReady 時間 var t = performance.timing var domReadyTime = t.domComplete - t.responseEnd; console.log(domReadyTime) |
其實就可以寫成:
1 2 3 |
window.performance.measure('domReady','responseEnd' , 'domComplete'); var domReadyMeasure = window.performance.getEntriesByName('domReady'); console.log(domReadyMeasure); |
拋磚引玉:performance 資料能幹啥用?
熟悉 Chrome 開發者工具的朋友應該知道:在開發環境下,其實我們自己開啟 Chrome 的開發者工具,切換到網路皮膚,就能很詳細的看到網頁效能相關的資料。但當我們需要統計分析使用者開啟我們網頁時的效能如何時,我們將 performance 原始資訊或通過簡單計算後的資訊(如上面寫到的 getPerformanceTiming() 和 getEntryTiming()) 上傳到伺服器,配合其他資訊(如 HTTP 請求頭資訊),就完美啦~