前端性能
1.關鍵點
分頁面、區域、瀏覽器、性能指標
頁面的性能指標詳解:
白屏時間(first Paint Time)——用戶從打開頁面開始到頁面開始有東西呈現為止
首屏時間——用戶瀏覽器首屏內所有內容都呈現出來所花費的時間
用戶可操作時間(dom Interactive)——用戶可以進行正常的點擊、輸入等操作,默認可以統計domready時間,因為通常會在這時候綁定事件操作
總下載時間——頁面所有資源都加載完成並呈現出來所花的時間,即頁面 onload 的時間
確定統計起點:
我們需要在用戶輸入 URL 或者點擊鏈接的時候就開始統計,因為這樣才能衡量用戶的等待時間。高端瀏覽器Navigation Timing接口;普通瀏覽器通過 cookie 記錄時間戳的方式來統計,需要注意的是 Cookie 方式只能統計到站內跳轉的數據。
2.如何統計性能指標的時間
2.1白屏時間
公式:
白屏時間=開始渲染時間(首字節時間+HTML下載完成時間)+頭部資源加載時間
如何獲取:
chrome 高版本:
window.chrome.loadTimes().firstPaintTime loadTimes獲取的結果
{ connectionInfo: "http/1", finishDocumentLoadTime: 1422412260.278667, finishLoadTime: 1422412261.083637, firstPaintAfterLoadTime: 1422412261.094726, firstPaintTime: 1422412258.085214, navigationType: "Reload", npnNegotiatedProtocol: "unknown", requestTime: 0, startLoadTime: 1422412256.920803, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: false, wasNpnNegotiated: false }
所以計算公式:
(chrome.loadTimes().firstPaintTime - chrome.loadTimes().startLoadTime)*1000
其他瀏覽器:
大部分瀏覽器沒有特定函數,必須想其他辦法來監測。仔細觀察 WebPagetest 視圖分析發現,白屏時間出現在頭部外鏈資源加載完附近,因為瀏覽器只有加載並解析完頭部資源才會真正渲染頁面。基於此我們可以通過獲取頭部資源加載完的時刻來近似統計白屏時間。盡管並不精確,但卻考慮了影響白屏的主要因素:首字節時間和頭部資源加載時間(HTML下載完成時間非常微小)。
有一個點:mod_36ad799.js等幾個js為什么會在hm.js之前下載?html代碼如下
這貌似與我們熟知的腳本阻塞解析不符啊,理應是腳本插入hm.js在先,導致DOM樹改變,重新繪制DOM樹,然后繼續往下解析……原因是現在的瀏覽器對這個過程做了優化:
處理腳本及樣式表的順序(The order of processing scripts and style sheets)
腳本
web的模式是同步的,開發者希望解析到一個script標簽時立即解析執行腳本,並阻塞文檔的解析直到腳本執行完。如果腳本是外引的,則網絡必須先請求到這個資源——這個過程也是同步的,會阻塞文檔的解析直到資源被請求到。這個模式保持了很多年,並且在html4及html5中都特別指定了。開發者可以將腳本標識為defer,以使其不阻塞文檔解析,並在文檔解析結束后執行。Html5增加了標記腳本為異步的選項,以使腳本的解析執行使用另一個線程。
預解析(Speculative parsing)
Webkit和Firefox都做了這個優化,當執行腳本時,另一個線程解析剩下的文檔,並加載后面需要通過網絡加載的資源。這種方式可以使資源並行加載從而使整體速度更快。需要注意的是,預解析並不改變Dom樹,它將這個工作留給主解析過程,自己只解析外部資源的引用,比如外部腳本、樣式表及圖片。
樣式表(Style sheets)
樣式表采用另一種不同的模式。理論上,既然樣式表不改變Dom樹,也就沒有必要停下文檔的解析等待它們,然而,存在一個問題,腳本可能在文檔的解析過程中請求樣式信息,如果樣式還沒有加載和解析,腳本將得到錯誤的值,顯然這將會導致很多問題,這看起來是個邊緣情況,但確實很常見。Firefox在存在樣式表還在加載和解析時阻塞所有的腳本,而Chrome只在當腳本試圖訪問某些可能被未加載的樣式表所影響的特定的樣式屬性時才阻塞這些腳本。
所以就得到了上面的那個結果
看看IE的處理
回歸正題,普通瀏覽器需要獲取兩個時間:開始渲染時間和頭部資源加載時間:
開始渲染時間:
需要借助瀏覽器的navigator timing屬性performance;window.performance.timing(Navigation timing性能時間線) 相關屬性:
// 在同一個瀏覽器上下文中,前一個網頁(與當前頁面不一定同域)unload 的時間戳,如果無前一個網頁 unload ,則與 fetchStart 值相等 navigationStart: 1441112691935, // 前一個網頁(與當前頁面同域)unload 的時間戳,如果無前一個網頁 unload 或者前一個網頁與當前頁面不同域,則值為 0 unloadEventStart: 0, unloadEventEnd: 0, // 第一個 HTTP 重定向發生時的時間。有跳轉且是同域名內的重定向才算,否則值為 0 redirectStart: 0, redirectEnd: 0, ... // 開始解析渲染 DOM 樹的時間,此時 Document.readyState 變為 loading,並將拋出 readystatechange 相關事件 domLoading: 1441112692690, ...
var timing = performance.timing; var loadingTime = timing .domLoading - timing.navigationStart;//開始渲染時間
看一下navigator timing瀏覽器支持情況
對於IE等低版本瀏覽器是不行的。
IE8 等低版本瀏覽器 通過 cookie 記錄時間戳的方式來統計,需要注意的是 Cookie 方式只能統計到站內跳轉的數據。 首次進入沒有好的統計方法。
頭部資源加載時間:
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"/> <script> var start_time = +new Date; //測試時間起點,實際統計起點為 DNS 查詢 </script> <!-- 3s 后這個 js 才會返回 --> <script src="script.php"></script> <script> var end_time = +new Date; //時間終點 var headtime = end_time - start_time; //頭部資源加載時間 console.log(headtime); </script> </head> <body> <p>在頭部資源加載完之前頁面將是白屏</p> <p>script.php 被模擬設置 3s 后返回,head 底部內嵌 JS 等待前面 js 返回后才執行</p> <p>script.php 替換成一個執行長時間循環的 js 效果也一樣</p> </body> </html>
這個比較簡單,在head的前面計時開始,在head最末尾計時結束,中間的差值就計算為頭部資源加載時間。
所以,最終計算方法:
var firstPaintTime = end_time - performance.timing.navigationStart
2.2首屏時間
首屏時間的統計比較復雜,因為涉及圖片等多種元素及異步渲染等方式。觀察加載視圖可發現,影響首屏的主要因素的圖片的加載。通過統計首屏內圖片的加載時間便可以獲取首屏渲染完成的時間。統計流程如下:
首屏位置調用 API 開始統計 -> 綁定首屏內所有圖片的 load 事件 -> 頁面加載完后判斷圖片是否在首屏內,找出加載最慢的一張 -> 首屏時間
這是同步加載情況下的簡單統計邏輯,另外需要注意的幾點:
- 頁面存在 iframe 的情況下也需要判斷加載時間
- gif 圖片在 IE 上可能重復觸發 load 事件需排除
- 異步渲染的情況下應在異步獲取數據插入之后再計算首屏
- css 重要背景圖片可以通過 JS 請求圖片 url 來統計(瀏覽器不會重復加載)
- 沒有圖片則以統計 JS 執行時間為首屏,即認為文字出現時間
//IE gif重復onload解決 var img=new Image(); img.load=function(){ //do something img.load=null;//重新賦值為null } img.src='××.gif';
統計方法1:
原理:在首屏渲染之前埋上處理邏輯,使用定時器不斷的去檢測img節點的圖片。判斷圖片是否在首屏和加載完成,找到首屏中加載時間最慢的的圖片完成的時間,從而計算出首屏時間。如果首屏有沒有圖片,如果沒圖片就用domready時間。
缺點: 1.瀏覽器定時器最大精度為55ms 2.背景圖片加載沒有計算在內 3.不斷檢測並執行的腳本耗時

//1,獲取首屏基線高度 //2,計算出基線dom元素之上的所有圖片元素 //3,所有圖片onload之后為首屏顯示時間 function getOffsetTop(ele) { var offsetTop = ele.offsetTop; if (ele.offsetParent !== null) { offsetTop += getOffsetTop(ele.offsetParent); } return offsetTop; } var firstScreenHeight = win.screen.height; var firstScreenImgs = []; var isFindLastImg = false; var allImgLoaded = false; var t = setInterval(function() { var i, img; if (isFindLastImg) { if (firstScreenImgs.length) { for (i = 0; i < firstScreenImgs.length; i++) { img = firstScreenImgs[i]; if (!img.complete) { allImgLoaded = false; break; } else { allImgLoaded = true; } } } else { allImgLoaded = true; } if (allImgLoaded) { collect.add({ firstScreenLoaded: startTime - Date.now() }); clearInterval(t); } } else { var imgs = body.querySelector('img'); for (i = 0; i < imgs.length; i++) { img = imgs[i]; var imgOffsetTop = getOffsetTop(img); if (imgOffsetTop > firstScreenHeight) { isFindLastImg = true; break; } else if (imgOffsetTop <= firstScreenHeight && !img.hasPushed) { img.hasPushed = 1; firstScreenImgs.push(img); } } } }, 0); doc.addEventListener('DOMContentLoaded', function() { var imgs = body.querySelector('img'); if (!imgs.length) { isFindLastImg = true; } }); win.addEventListener('load', function() { allImgLoaded = true; isFindLastImg = true; if (t) { clearInterval(t); } collect.log(collect.global); });
統計方法2:
原理:對於網頁高度小於屏幕的網站來說,只要在頁面底部加上腳本打印當前時間即可;或者對於網頁高度大於一屏的網頁來說,只要在估算接近於一屏幕的元素的位置后,打印一下當前時間。當然這個時間要得把首屏中所有圖片的加載時間也算上。
缺點: 1.需要每個頁面手動加入到對應位置 2.背景圖片加載沒有計算在內

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0"> <script type="text/javascript"> window.logInfo = {}; window.logInfo.openTime = performance.timing.navigationStart; </script> </head> <body> <div>這是第一屏,這是第一屏</div> <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png"> <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png"> <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png"> <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png"> <div>第一屏結尾,第一屏結尾</div> <script type="text/javascript"> (function logFirstScreen() { var images = document.getElementsByTagName('img'); var iLen = images.length; var curMax = 0; var inScreenLen = 0; // 圖片的加載回調 function imageBack() { this.removeEventListener && this.removeEventListener('load', imageBack, !1); if (++curMax === inScreenLen) { // 如果所有在首屏的圖片均已加載完成了的話,發送日志 log(); } } // 對於所有的位於指定區域的圖片,綁定回調事件 for (var s = 0; s < iLen; s++) { var img = images[s]; var offset = { top: 0 }; var curImg = img; while (curImg.offsetParent) { offset.top += curImg.offsetTop; curImg = curImg.offsetParent; } // 判斷圖片在不在首屏 if (document.documentElement.clientHeight < offset.top) { continue; } // 圖片還沒有加載完成的話 if (!img.complete) { inScreenLen++; img.addEventListener('load', imageBack, !1); } } // 如果首屏沒有圖片的話,直接發送日志 if (inScreenLen === 0) { log(); } // 發送日志進行統計 function log () { window.logInfo.firstScreen = +new Date() - window.logInfo.openTime; console.log('首屏時間:', window.logInfo.firstScreen + 'ms'); } })(); </script> <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png"> <img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png"> </body> </html>
2.3統計用戶可操作
用戶可操作為所有DOM都解析完畢的時間,默認可以統計domready時間,因為通常會在這時候綁定事件操作。對於使用了模塊化異步加載的 JS 可以在代碼中去主動標記重要 JS 的加載時間,這也是產品指標的統計方式。
使用jquery中的$(document).ready()即是此意義 window.performance.timing.domInteractive window.performance.timing.domContentLoadedEventStart
計算公式:
performance.timing.domInteractive - performance.timing.navigationStart
2.4總下載時間
默認可以統計onload時間,這樣可以統計同步加載的資源全部加載完的耗時。如果頁面中存在很多異步渲染,可以將異步渲染全部完成的時間作為總下載時間。
計算公式:
performance.timing.loadEventStart- performance.timing.navigationStart
2.5統計api相關
片段摘自:美團性能分析框架和性能監控平台,並加入部分其他文字
對於統計腳本,需要滿足兩個條件:
- 避免對業務代碼的入侵;(獨立的腳本)
- 不影響被測量的頁面的性能;(主文檔加載完畢之后,再注入統計腳本收集數據,並且盡可能的合並數據請求,減少帶寬消耗。)
確定了數據統計腳本的約束條件之后,我們從哪里得到這些數據呢?目前使用的主要途徑有:
- 主文檔加載速度,利用 Navigation Timing API 取得;
- 靜態資源加載速度,利用 Resource Timing API 取得;
- 首次渲染速度,IE 下用 msFirstPaint(window.performance.timing.msFirstPaint) 取得,Chrome 下利用 loadTimes(window.chrome.loadTimes()) 取得,我們的 Chrome 瀏覽器用戶占比超過 70%;
- 文檔生成速度,則是在后端應用內打點來獲得;
對於主文檔加載速度,我們從宏觀到微觀的做了這樣的分解,從上到下的時間流,右邊的時刻標記了每個指標從哪里開始計算到哪里截止,比如,跳轉時間 redirect
由 redirectEnd - redirectStart
計算得到,其他的類推:
采集主文檔加載速度的具體做法是:
- 在主文檔 load 之前提供可緩存數據的接口,方便在統計腳本載入前就可以准備數據;
- 在主文檔 load 之后注入數據收集腳本,該腳本加載完成之后會處理所有的數據;
- 利用 Navigation Timing API 收集計算得到上圖中的指標;
- 給所有數據打上頁面、地理位置、瀏覽器等標簽,方便更細維度的分析;
對於靜態資源的加載速度,我們也做了類似的分解和采集(使用resource timing API):
需要特別提示的是,如果你使用 CDN 的話,需要讓 CDN 服務商加上 Timing-Allow-Origin 的響應頭,才能拿到靜態資源的數據。
而對於主文檔生成速度,我們則開發了性能統計的 Library,在框架級別集成后端性能的時間指標。
- High Resolution Timing(高精度計時)
該API規范所定義的JavaScript接口能夠提供精確到微秒級的當前時間,並且不會受到系統時鍾偏差或調整的影響。對於性能分析來說,精確的測量結果意義重大。
var perf = performance.now(); // console output 439985.4570000316
- Page Visibility (頁面可見性)
通過這一規范,網站開發者能夠以編程方式確定頁面的當前可見狀態,從而使網站能夠更有效地利用電源與CPU。
當頁面獲得或失去焦點時,文檔對象的visibilitychange事件便會被觸發。
document.addEventListener('visibilitychange', function(event){if(document.hidden){// Page currently hidden.}else{// Page currently visible.}});
這一事件對於了解頁面的可見狀態十分有用,舉例來說,用戶可能會同時打開多個瀏覽器標簽,而你希望只在用戶顯示你的網站頁面時才進行某些操作(比如播放一段音頻文件、或是執行一段JavaScript動畫),就可以通過這一事件進行觸發。對於移動設備來說,如果用戶在某個標簽中打開了你的網站,但正在另一個標簽中瀏覽其它內容時,這一特性能夠節省該設備的電池消耗。(雖然對於你的網站性能來說意義不大……)
其它部分API功能簡介
- Resource Timing(資源計時)——對單個資源(如圖片)的計時,可以對細粒度的用戶體驗進行檢測。
- Performance Timeline(性能時間線)——以一個統一的接口獲取由Navigation Timing、Resourcing Timing和User Timing所收集的性能數據。
- Battery Status(電池狀態)——能夠檢測當前設備的電池狀態,例如是否正在充電、電量等級等等。可以根據當前電量決定是否顯示某些內容(例如視頻、動畫等等),對於移動設備來說非常實用。
- User Timing(用戶計時)——可以對某段代碼、函數進行自定義計時,以了解這段代碼的具體運行時間,類似於stop watch的作用。
- Beacon(燈塔)——可以將分析結果或診斷代碼發送給服務器,它采用了異步執行的方式,因此不會影響頁面中其它代碼的運行。對於收集測試結果並進行統計分析來說是一種十分便利的工具。
- Animation Timing(動畫計時) - 通過requestAnimationFrame函數讓瀏覽器精通地控制動畫的幀數,能夠有效地配合顯示器的刷新率,提供更平滑的動畫效果,減少對CPU和電池的消耗。
- Resource Hits(資源提示) - 通過html屬性指定資源的預加載,例如在瀏覽相冊時能夠預先加載下一張圖片,加快翻頁的顯示速度。
- Frame Timing(幀計時)——通過一個接口獲取與幀相關的性能數據,例如每秒幀數和TTF。該標准目前尚未被支持。
- Navigation Error Logging(導航錯誤日志記錄)——通過一個接口存儲及獲取與某個文檔的導航相關的錯誤記錄。該標准目前尚未被支持。
瀏覽器支持
下表列舉了當前主流瀏覽器對性能API的支持,其中標注星號的內容並非來自於Web性能工作小組。
規范 | Internet Explorer | Firefox | Chrome | Safari | Opera | iOS Safari | Android |
Navigation Timing | 9 | 31 | 全部 | 8 | 26 | 8 (不包括 8.1) | 4.1 |
High Resolution Timing | 10 | 31 | 全部 | 8 | 26 | 8 (不包括 8.1) | 4.4 |
Page Visibility | 10 | 31 | 全部 | 7 | 26 | 7.1 | 4.4 |
Resource Timing | 10 | 34 | 全部 | - | 26 | - | 4.4 |
Battery Status* | - | 31 (部分支持) | 38 | - | 26 | - | - |
User Timing | 10 | - | 全部 | - | 26 | - | 4.4 |
Beacon | - | 31 | 39 | - | 26 | - | - |
Animation Timing | 10 | 31 | 全部 | 6.1 | 26 | 7.1 | 4.4 |
Resource Hints | - | - | 僅限Canary版 | - | - | - | - |
Frame Timing | - | - | - | - | - | - | - |
Navigation Error Logging | - | - | - | - | - | - | - |
WebP* | - | - | 全部 | - | 26 | - | 4.1 |
Picture element and srcset attribute * | - | - | 38 | - | 26 | - | - |
其它
DZone.com在《Performance & Monitoring 2015》這份白皮書中專門介紹了性能API以及W3C所推薦的新協議、標准及HTML元素,並提供了簡單的示例。可以在這里下載完整的白皮書(需要注冊)。本文中的示例代碼即來自於該白皮書。
如果想了解有關Web性能API的更多內容,可以參考W3C官方文檔或這篇博客。
2.6 性能優化分為兩個階段來做
1.使用測試工具自測和優化(工具如ySlow/,線上工具www.webpagetest.org、阿里測、gtmetrix)
ySlow/ShowSlow:http://www.showslow.com/ 【前端性能監控系統,前端性能指標數據展示,無法實現自動化監控用戶真實的應用場景,針對移動端的性能監控,目前由於其本身依賴的工具絕大多數只有PC端,在移動端缺乏相應的數據上報工具(特別是移動端本身復雜的網絡環境),所以如果想使用ShowSlow作為前端性能監控平台,需要單獨實現數據收集系統,而只是將ShowSlow當作展示系統使用,開源】
Page Speed: 【基於一系列優化規則對網站進行檢測,類似的有Yslow(推薦使用https://gtmetrix.com/來檢測網站性能和規則,使用不同的工具檢測對比) 】
阿里測:基於WebPageTest,網頁前端性能測試工具;
PhantomJS:自動化監測,模擬Phantom JS 是一個服務器端的 JavaScript API 的 WebKit,基於它可以輕松實現 web 自動化測試。類似的有berserkJS。但是都是服務器模擬測試,不能監控用戶真實環境。
webpagetest線上版
基於WebPagetest的阿里測(已下線,不舉例了,上17測:http://www.17ce.com/)
綜合了pagespeed和ySlow的GTmetrix
2.啟用線上監控用戶真實情況(前端性能監控平台)
看幾個例子
透視寶:http://www.toushibao.com/brower.html 【前端性能上報,圖表展示,監控用戶真實的應用場景,付費】
提供了每一個請求的詳細信息
可以按選擇的字段排序,通過過濾進行類似數據對比,可以查看每一個請求的詳細信息,不過按url搜索貌似沒有用,如果這個有用的話那就可以對同一個頁面做時間的對比。
優點:
1.柱狀時間線排列
2.多個指標同時展示,便於比較
缺點:
1.缺少部分關鍵時間(白屏時間=首字節時間+HTML下載完成時間+頭部資源加載時間)
2.性能沒有按地區分類,參考價值大大減少
3.免費版本只存儲3天
Browser Insight:http://www.oneapm.com/bi/feature.html 【前端性能上報,圖表展示,監控用戶真實的應用場景,付費】
要查看某次訪問的詳情需要在雲快照中拍照,模擬訪問
優點:
1.指標齊全
2.慢加載追蹤所有資源加載情況
缺點:
1.四個性能指標沒有按地區分類的數據,參考價值大大減少
mmtrix(性能魔方):http://www.mmtrix.com/
先看評測
http://www.7k7k.com WEB評測實例:統計數據不錯
真實用戶性能監控:
優點:
1.支持不同地域的四個關鍵性能指標的展示
2.支持展示不同區間的數據比例
缺點:
1.不支持https協議
還有一個國內較大的性能監控平台聽雲
指標比價少,沒有太多價值。就不做比較了。
看一下國外的性能監控網站
先看newrelic(rpm.newrelic.com),注冊需要自己使用外網代理
和OneAPM很像,前端性能指標不全。用來監控ajax請求, js報錯等還不錯,但是滿足不了我的需求。
appdynamics(www.appdynamics.com)
注冊居然找不到中國
隨便選了一個canmroon
和透視寶很像。免費版本保存時間更少,只有24小時。
總的來說,mmtrix和OneAPM指標更全一些。還沒有研究他們的監控代碼,不知道監控的指標正確與否。公司的性能這塊也剛起步,離優秀還有很大一段距離,就寫到這里了,希望對研究性能剛起步的童鞋有點作用,任務還很重,加油。