在一樣的網絡環境下,有兩個一樣能知足你的需求的網站,一個唰的一下就加載出來了,另外一個白屏轉圈轉了半天內容纔出來,若是讓你選擇,你會用哪個?javascript
頁面的性能問題是前端開發中一個重要環節,但一直以來咱們沒有比較好的手段,來檢測頁面的性能。直到W3C性能小組引入的新的API window.performance,目前IE9以上的瀏覽器都支持。它是一個瀏覽器中用於記錄頁面加載和解析過程當中關鍵時間點的對象。放置在global環境下,經過JavaScript能夠訪問到它。css
你能夠經過如下方法來探測和兼容performance:html
var performance = window.performance ||
window.msPerformance ||
window.webkitPerformance;
if (performance) {
// 你的代碼
}
複製代碼
先來了解一下performance的結構:前端
performance.memory是顯示此刻內存佔用狀況,它是一個動態值,其中: usedJSHeapSize表示:JS 對象(包括V8引擎內部對象)佔用的內存數 totalJSHeapSize表示:可以使用的內存 jsHeapSizeLimit表示:內存大小限制 一般,usedJSHeapSize不能大於totalJSHeapSize,若是大於,有可能出現了內存泄漏。vue
performance.navigation顯示頁面的來源信息,其中: redirectCount表示:若是有重定向的話,頁面經過幾回重定向跳轉而來,默認爲0 type表示頁面打開的方式, 0 表示 TYPE_NAVIGATENEXT 正常進入的頁面(非刷新、非重定向等) 1 表示 TYPE_RELOAD 經過 window.location.reload() 刷新的頁面 2 表示 TYPE_BACK_FORWARD 經過瀏覽器的前進後退按鈕進入的頁面(歷史記錄) 255 表示 TYPE_UNDEFINED 非以上方式進入的頁面java
performance.onresourcetimingbufferfull 屬性是一個在resourcetimingbufferfull事件觸發時會被調用的 event handler 。它的值是一個手動設置的回調函數,這個回調函數會在瀏覽器的資源時間性能緩衝區滿時執行。react
performance.timeOrigin是一系列時間點的基準點,精確到萬分之一毫秒。git
performance.timing是一系列關鍵時間點,它包含了網絡、解析等一系列的時間數據。github
下面是對這些時間點進行解釋web
timing: {
// 同一個瀏覽器上一個頁面卸載(unload)結束時的時間戳。若是沒有上一個頁面,這個值會和fetchStart相同。
navigationStart: 1543806782096,
// 上一個頁面unload事件拋出時的時間戳。若是沒有上一個頁面,這個值會返回0。
unloadEventStart: 1543806782523,
// 和 unloadEventStart 相對應,unload事件處理完成時的時間戳。若是沒有上一個頁面,這個值會返回0。
unloadEventEnd: 1543806782523,
// 第一個HTTP重定向開始時的時間戳。若是沒有重定向,或者重定向中的一個不一樣源,這個值會返回0。
redirectStart: 0,
// 最後一個HTTP重定向完成時(也就是說是HTTP響應的最後一個比特直接被收到的時間)的時間戳。
// 若是沒有重定向,或者重定向中的一個不一樣源,這個值會返回0.
redirectEnd: 0,
// 瀏覽器準備好使用HTTP請求來獲取(fetch)文檔的時間戳。這個時間點會在檢查任何應用緩存以前。
fetchStart: 1543806782096,
// DNS 域名查詢開始的UNIX時間戳。
//若是使用了持續鏈接(persistent connection),或者這個信息存儲到了緩存或者本地資源上,這個值將和fetchStart一致。
domainLookupStart: 1543806782096,
// DNS 域名查詢完成的時間.
//若是使用了本地緩存(即無 DNS 查詢)或持久鏈接,則與 fetchStart 值相等
domainLookupEnd: 1543806782096,
// HTTP(TCP) 域名查詢結束的時間戳。
//若是使用了持續鏈接(persistent connection),或者這個信息存儲到了緩存或者本地資源上,這個值將和 fetchStart一致。
connectStart: 1543806782099,
// HTTP(TCP) 返回瀏覽器與服務器之間的鏈接創建時的時間戳。
// 若是創建的是持久鏈接,則返回值等同於fetchStart屬性的值。鏈接創建指的是全部握手和認證過程所有結束。
connectEnd: 1543806782227,
// HTTPS 返回瀏覽器與服務器開始安全連接的握手時的時間戳。若是當前網頁不要求安全鏈接,則返回0。
secureConnectionStart: 1543806782162,
// 返回瀏覽器向服務器發出HTTP請求時(或開始讀取本地緩存時)的時間戳。
requestStart: 1543806782241,
// 返回瀏覽器從服務器收到(或從本地緩存讀取)第一個字節時的時間戳。
//若是傳輸層在開始請求以後失敗而且鏈接被重開,該屬性將會被數製成新的請求的相對應的發起時間。
responseStart: 1543806782516,
// 返回瀏覽器從服務器收到(或從本地緩存讀取,或從本地資源讀取)最後一個字節時
//(若是在此以前HTTP鏈接已經關閉,則返回關閉時)的時間戳。
responseEnd: 1543806782537,
// 當前網頁DOM結構開始解析時(即Document.readyState屬性變爲「loading」、相應的 readystatechange事件觸發時)的時間戳。
domLoading: 1543806782573,
// 當前網頁DOM結構結束解析、開始加載內嵌資源時(即Document.readyState屬性變爲「interactive」、相應的readystatechange事件觸發時)的時間戳。
domInteractive: 1543806783203,
// 當解析器發送DOMContentLoaded 事件,即全部須要被執行的腳本已經被解析時的時間戳。
domContentLoadedEventStart: 1543806783203,
// 當全部須要當即執行的腳本已經被執行(不論執行順序)時的時間戳。
domContentLoadedEventEnd: 1543806783216,
// 當前文檔解析完成,即Document.readyState 變爲 'complete'且相對應的readystatechange 被觸發時的時間戳
domComplete: 1543806783796,
// load事件被髮送時的時間戳。若是這個事件還未被髮送,它的值將會是0。
loadEventStart: 1543806783796,
// 當load事件結束,即加載事件完成時的時間戳。若是這個事件還未被髮送,或者還沒有完成,它的值將會是0.
loadEventEnd: 1543806783802
}
複製代碼
這些參數很是有用,能夠幫助咱們獲取頁面的Domready時間、onload時間、白屏時間等,以及單個頁面資源在從發送請求到獲取到rsponse各階段的性能參數。
對咱們比較有用的頁面性能數據大概包括以下幾個,這些參數是經過上面的performance.timing各個屬性的差值組成的,它是精確到毫秒的一個值,計算方法以下:
**重定向優化:**重定向的類型分三種,301(永久重定向),302(臨時重定向),304(Not Modified)。304是用來優化緩存,很是有用,而前兩種應該儘量的避免,凡是遇到須要重定向跳轉代碼的代碼,能夠把重定向以後的地址直接寫到前端的html或JS中,能夠減小客戶端與服務端的通訊過程,節省重定向耗時。
**DNS優化:**通常來講,在前端優化中與 DNS 有關的有兩點: 一個是減小DNS的請求次數,另外一個就是進行DNS預獲取(Prefetching ) 。典型的一次DNS解析須要耗費 20-120 毫秒(移動端會更慢),減小DNS解析的次數是個很好的優化方式,儘可能把各類資源放在一個cdn域名上。DNS Prefetching 是讓具備此屬性的域名不須要用戶點擊連接就在後臺解析,而域名解析和內容載入是串行的網絡操做,因此這個方式能減小用戶的等待時間,提高用戶體驗 。新版的瀏覽器會對頁面中和當前域名(正在瀏覽網頁的域名)不在同一個域的域名進行預獲取,而且緩存結果,這就是隱式的 DNS Prefetch。若是想對頁面中沒有出現的域進行預獲取,那麼就要使用顯示的 DNS Prefetch 了。下圖是DNS Prefetch的方法:
<html>
<head>
<title>騰訊網</title>
<link rel="dns-prefetch" href="//mat1.gtimg.com" />
<link rel="dns-prefetch" href="//inews.gtimg.com" />
<link rel="dns-prefetch" href="//wx.qlogo.cn" />
<link rel="dns-prefetch" href="//coral.qq.com" />
<link rel="dns-prefetch" href="//pingjs.qq.com" />
複製代碼
**TCP請求優化:**TCP的優化大都在服務器端,前端能作的就是儘可能減小TCP的請求數,也就是減小HTTP的請求數量。http 1.0 默認使用短鏈接,也是TCP的短鏈接,也就是客戶端和服務端每進行一次http操做,就創建一次鏈接,任務結束就中斷鏈接。這個過程當中有3次TCP請求握手和4次TCP請求釋放。減小TCP請求的方式有兩種,一種是資源合併,對於頁面內的圖片、css和js進行合併,減小請求量。另外一種使用長連接,使用http1.1,在HTTP的響應頭會加上 Connection:keep-alive,當一個網頁打開完成以後,鏈接不會立刻關閉,再次訪問這個服務時,會繼續使用這個長鏈接。這樣就大大減小了TCP的握手次數和釋放次數。或者使用Websocket進行通訊,全程只須要創建一次TCP連接。
**HTTP請求優化:**使用內容分發網絡(CDN)和減小請求。使用CDN能夠減小網絡的請求時延,CDN的域名不要和主站的域名同樣,這樣會防止訪問CDN時還攜帶主站cookie的問題,對於網絡請求,可使用fetch發送無cookie的請求,減小http包的大小。也可使用本地緩存策略,儘可能減小對服務器數據的重複獲取。
**渲染優化:**在瀏覽器端的渲染過程,如大型框架,vue和react,它的模板其實都是在瀏覽器端進行渲染的,不是直出的html,而是要走框架中相關的框架代碼才能去渲染出頁面,這個渲染過程對於首屏就有較大的損耗,白屏的時間會有所增長。在必要的狀況下能夠在服務端進行整個html的渲染,從而將整個html直出到咱們的瀏覽器端,而非在瀏覽器端進行渲染。
還有一個問題就是,在默認狀況下,JavaScript 執行會「阻止解析器」,當瀏覽器遇到一個 script 外鏈標記時,DOM 構建將暫停,會將控制權移交給 JavaScript 運行時,等腳本下載執行完畢,而後再繼續構建 DOM。並且內聯腳本始終會阻止解析器,除非編寫額外代碼來推遲它們的執行。咱們能夠把 script 外鏈加入到頁面底部,也可使用 defer 或 async 延遲執行。defer 和 async 的區別就是 defer 是有序的,代碼的執行按在html中的前後順序,而 async 是無序的,只要下載完畢就會當即執行。或者使用異步的編程方法,好比settimeout,也可使用多線webworker,它們不會阻礙 DOM 的渲染。
<script async type="text/javascript" src="app1.js"></script>
<script defer type="text/javascript" src="app2.js"></script>
複製代碼
performance.timing記錄的是用於分析頁面總體性能指標。若是要獲取個別資源(例如JS、圖片)的性能指標,就須要使用Resource Timing API。 **performance.getEntries()**方法,包含了全部靜態資源的數組列表;每一項是一個請求的相關參數有name,type,時間等等。下圖是chrome顯示騰訊網的相關資源列表。
能夠看到,與 performance.timing 對比: 沒有與 DOM 相關的屬性,新增了name
、entryType
、initiatorType
和duration
四個屬性。它們是
<script>
,「img」即performance.timing
得到。或者資源服務器開啓響應頭Timing-Allow-Origin,添加指定來源站點,以下所示:Timing-Allow-Origin: https://qq.com
複製代碼
除了performance.getEntries
以外,performance
還包含一系列有用的方法。以下圖
performance.now() performance.now()
返回一個當前頁面執行的時間的時間戳,用來精確計算程序執行時間。與 Date.now()
不一樣的是,它使用了一個浮點數,返回了以毫秒爲單位,小數點精確到微秒級別的時間,更加精準。而且不會受系統程序執行阻塞的影響,performance.now()
的時間是以恆定速率遞增的,不受系統時間的影響(系統時間可被人爲或軟件調整)。performance.timing.navigationStart + performance.now()
約等於 Date.now()
。
let t0 = window.performance.now();
doSomething();
let t1 = window.performance.now();
console.log("doSomething函數執行了" + (t1 - t0) + "毫秒.")
複製代碼
經過這個方法,咱們能夠用來測試某一段代碼執行了多少時間。
performance.mark() mark方法用來自定義添加標記時間。使用方法以下:
var nameStart = 'markStart';
var nameEnd = 'markEnd';
// 函數執行前作個標記
window.performance.mark(nameStart);
for (var i = 0; i < n; i++) {
doSomething
}
// 函數執行後再作個標記
window.performance.mark(nameEnd);
// 而後測量這個兩個標記間的時間距離,並保存起來
var name = 'myMeasure';
window.performance.measure(name, nameStart, nameEnd);
複製代碼
保存後的值能夠經過 ** performance.getEntriesByname( 'myMeasure' )**或者 performance.getEntriesByType('measure')查詢。
Performance.clearMeasures() 從瀏覽器的性能輸入緩衝區中移除自定義添加的 measure
Performance.getEntriesByName() 返回一個 PerformanceEntry 對象的列表,基於給定的 name 和 entry type
Performance.getEntriesByType() 返回一個 PerformanceEntry 對象的列表,基於給定的 entry type
Performance.measure() 在瀏覽器的指定 start mark 和 end mark 間的性能輸入緩衝區中建立一個指定名稱的時間戳,見上例
Performance.toJSON() 是一個 JSON 格式轉化器,返回 Performance 對象的 JSON 對象
Performance.setResourceTimingBufferSize() 設置當前頁面可緩存的最大資源數據個數,entryType爲resource的資源數據個數。超出時,會清空全部entryType爲resource的資源數據。參數爲整數(maxSize)。配合performance.onresourcetimingbufferfull事件能夠有效監控資源緩衝區。當entryType爲resource的資源數量超出設置值的時候會觸發該事件。 Performance.clearResourceTimings() 從瀏覽器的性能數據緩衝區中移除全部的 entryType 是 "resource" 的 performance entries 下面是mdn上關於這個屬性的一個demo。這個demo的主要內容是當緩衝區內容滿時,調用buffer_full函數。
function buffer_full(event) {
console.log("WARNING: Resource Timing Buffer is FULL!");
performance.setResourceTimingBufferSize(200);
}
function init() {
// Set a callback if the resource buffer becomes filled
performance.onresourcetimingbufferfull = buffer_full;
}
<body onload="init()">
複製代碼
使用performance的這些屬性和方法,可以準確的記錄下咱們想要的時間,再加上日誌採集等功能的輔助,咱們就能很容易的掌握本身網站的各項性能指標了。
目前主流瀏覽器雖然都已支持performance對象,可是並不能支持它上面的所有屬性和方法,有些細微的差異。本文主要依據chrome和qq瀏覽器測試了相關屬性和方法,都可使用。
如今的不少性能監控分析工具都是經過數據上報來實現的,不能及時有效的反饋頁面的性能問題,只能在用戶使用以後上報(問題出現以後)才能知道。因此基於新聞前端團隊基於performance API作了一款實時查看性能的的工具,它並能給出詳細的報表,在開發階段把性能問題給解決掉。
superProfiler**【外部開源流程中】**
它是一款JavaScript性能監控工具庫,經過腳本引用,加載展現在頁面右側,無須依賴任何庫和腳本,能夠實時查看當前頁面的FPS、代碼執行耗時、內存佔用以及當前頁面的網絡性能,資源佔用。
還能查看最近的(10次)頁面性能的平均數。點擊「生成報表」按鈕會生成更詳細的數據報表概覽。
Performance API 用來作前端性能監控很是有用,它提供了不少方便測試咱們程序性能的接口。好比mark和measure。不少優秀的框架也用到了這個API進行測試。它裏面就頻繁用到了mark和measure來測試程序性能。因此想要開發高性能的web程序,瞭解Performace API仍是很是重要的。最後經過superProfiler工具能夠更快更便捷的查找出性能問題,針對性的擊破問題,提升開發效率,提高用戶體驗。固然這只是前端性能優化的第一步,道阻且長。但願你們提出問題和指出疑問,一塊兒進步。
最後,騰訊新聞TNFE前端團隊爲前端開發人員整理出了小程序以及web前端技術領域的最新優質內容,每週更新✨,歡迎star,github地址:github.com/Tnfe/TNFE-W…