作者: 騰訊新聞前端團隊
在同樣的網絡環境下,有兩個同樣能滿足你的需求的網站,一個唰的一下就加載出來了,另一個白屏轉圈轉了半天內容才出來,如果讓你選擇,你會用哪一個?
頁面的性能問題是前端開發中一個重要環節,但一直以來我們沒有比較好的手段,來檢測頁面的性能。直到W3C性能小組引入的新的API window.performance,目前IE9以上的瀏覽器都支持。它是一個瀏覽器中用于記錄頁面加載和解析過程中關鍵時間點的對象。放置在global環境下,通過JavaScript可以訪問到它。
使用性能API
你可以通過以下方法來探測和兼容performance:
var performance = window.performance ||
先來了解一下performance的結構:
performance.memory是顯示此刻內存占用情況,它是一個動態值,其中:
usedJSHeapSize表示:JS 對象(包括V8引擎內部對象)占用的內存數
totalJSHeapSize表示:可使用的內存
jsHeapSizeLimit表示:內存大小限制
通常,usedJSHeapSize不能大于totalJSHeapSize,如果大于,有可能出現了內存泄漏。
performance.navigation顯示頁面的來源信息,其中:
redirectCount表示:如果有重定向的話,頁面通過幾次重定向跳轉而來,默認為0
type表示頁面打開的方式,
0 表示 TYPE_NAVIGATENEXT 正常進入的頁面(非刷新、非重定向等)
1 表示 TYPE_RELOAD 通過 window.location.reload() 刷新的頁面
2 表示 TYPE_BACK_FORWARD 通過瀏覽器的前進后退按鈕進入的頁面(歷史記錄)
255 表示 TYPE_UNDEFINED 非以上方式進入的頁面
performance.onresourcetimingbufferfull 屬性是一個在resourcetimingbufferfull事件觸發時會被調用的 event handler 。它的值是一個手動設置的回調函數,這個回調函數會在瀏覽器的資源時間性能緩沖區滿時執行。
performance.timeOrigin是一系列時間點的基準點,精確到萬分之一毫秒。
performance.timing是一系列關鍵時間點,它包含了網絡、解析等一系列的時間數據。
下面是對這些時間點進行解釋
timing: {
這些參數非常有用,可以幫助我們獲取頁面的Domready時間、onload時間、白屏時間等,以及單個頁面資源在從發送請求到獲取到rsponse各階段的性能參數。
對我們比較有用的頁面性能數據大概包括如下幾個,這些參數是通過上面的performance.timing各個屬性的差值組成的,它是精確到毫秒的一個值,計算方法如下:
重定向耗時:redirectEnd - redirectStart
DNS查詢耗時 :domainLookupEnd - domainLookupStart
TCP鏈接耗時 :connectEnd - connectStart
HTTP請求耗時 :responseEnd - responseStart
解析dom樹耗時 : domComplete - domInteractive
白屏時間 :responseStart - navigationStart
DOMready時間 :domContentLoadedEventEnd - navigationStart
onload時間:loadEventEnd - navigationStart,也即是onload回調函數執行的時間。
如何優化?
重定向優化:重定向的類型分三種,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>
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>
資源性能API
performance.timing記錄的是用于分析頁面整體性能指標。如果要獲取個別資源(例如JS、圖片)的性能指標,就需要使用Resource Timing API。
performance.getEntries()方法,包含了所有靜態資源的數組列表;每一項是一個請求的相關參數有name,type,時間等等。下圖是chrome顯示騰訊網的相關資源列表。
可以看到,與 performance.timing 對比: 沒有與 DOM 相關的屬性,新增了name
、entryType
、initiatorType
和duration
四個屬性。它們是:
name表示:資源名稱,也是資源的絕對路徑,可以通過performance.getEntriesByName(name屬性的值),來獲取這個資源加載的具體屬性。
entryType表示:資源類型 "resource",還有“navigation”, “mark”, 和 “measure”另外3種。
- initiatorType表示:請求來源 "link",即表示<link> 標簽,還有“script”即 <script>,“img”即<img>標簽,“css”比如background的url方式加載資源以及“redirect”即重定向 等。
- duration表示:加載時間,是一個毫秒數字。
受同源策略影響,跨域資源獲取到的時間點,通常為0,如果需要更詳細準確的時間點,可以單獨請求資源通過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();
通過這個方法,我們可以用來測試某一段代碼執行了多少時間。
performance.mark()
mark方法用來自定義添加標記時間。使用方法如下:
var nameStart = 'markStart';
保存后的值可以通過 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) {
使用performance的這些屬性和方法,能夠準確的記錄下我們想要的時間,再加上日志采集等功能的輔助,我們就能很容易的掌握自己網站的各項性能指標了。
兼容性
目前主流瀏覽器雖然都已支持performance對象,但是并不能支持它上面的全部屬性和方法,有些細微的差別。本文主要依據chrome和qq瀏覽器測試了相關屬性和方法,均可使用。
我們做了什么?****(劃重點)
現在的很多性能監控分析工具都是通過數據上報來實現的,不能及時有效的反饋頁面的性能問題,只能在用戶使用之后上報(問題出現之后)才能知道。所以基于新聞前端團隊基于performance API做了一款實時查看性能的的工具,它并能給出詳細的報表,在開發階段把性能問題給解決掉。
superProfiler【外部開源流程中】
它是一款JavaScript性能監控工具庫,通過腳本引用,加載展示在頁面右側,無須依賴任何庫和腳本,可以實時查看當前頁面的FPS、代碼執行耗時、內存占用以及當前頁面的網絡性能,資源占用。
還能查看最近的(10次)頁面性能的平均數。點擊“生成報表”按鈕會生成更詳細的數據報表概覽。
小結
Performance API 用來做前端性能監控非常有用,它提供了很多方便測試我們程序性能的接口。比如mark和measure。很多優秀的框架也用到了這個API進行測試。它里面就頻繁用到了mark和measure來測試程序性能。所以想要開發高性能的web程序,了解Performace API還是非常重要的。最后通過superProfiler工具可以更快更便捷的查找出性能問題,針對性的擊破問題,提高開發效率,提升用戶體驗。當然這只是前端性能優化的第一步,道阻且長。希望大家提出問題和指出疑問,一起進步。
推薦閱讀
(點擊標題可跳轉閱讀)
覺得本文對你有幫助?請分享給更多人