項目上線後,一般都是都會作埋點,作數據監控、異常監控以及性能監控,本文主要聊聊前端性能監控。重點講performance
怎麼作性能監控。javascript
前端性能監控一般檢測某些方面的加載時間,經過獲得的加載時間的長短來判斷項目某方面的性能怎樣,具體是哪些方面呢???咱們先來看下頁面加載是一個怎樣的過程:css
頁面加載的方式有兩種,一種是加載完資源文件後經過javascript
動態獲取接口數據,而後數據返回渲染內容,這就是先後端分離的項目頁面加載方式。另外一種就是服務器渲染,同構直出前端頁面,這種方式相對前一種方式頁面加載的速度要快不少,性能方面相對也就要好些。可是目前大部分的項目都是先後端分離,本文重點描述這種方式。html
DNS
解析,將域名解析成IP
,若是緩存中存在,直接叢緩存中取IP
,不用作域名解析TCP
鏈接、三次握手:創建瀏覽器端和服務器端鏈接html
文件,構建DOM
樹,CSSOM
樹,js
文件的加載可能會阻塞頁面的渲染html
被徹底加載和解析後會觸發DOMContentLoaded
事件CSSOM
樹和DOM
樹構建完成後會開始生成Render
樹,繪製Render
樹的過程當中,瀏覽器就開始調用GPU
繪製,合成圖層,將內容顯示在屏幕上TCP
鏈接斷開上面就是頁面加載的基本過程,沒有展開講,若是要展開講的話,十篇文章也講不完,在本文中只須要清楚大概的整個過程,後期安排系統的整理上述整個過程的內容,作文字輸出。前端
頁面加載整個過程當中主要分爲:白屏、重定向、DNS
查詢、TCP
鏈接、HTTP
請求、DOM
解析、DOMready
、onload
等,這些也就是咱們前端性能監控包括的主要方面。咱們須要監控這幾個方面的數據,作數據分析,進一步作前端性能優化。例如如何加快首屏加載時間、減小http
請求時間等等。vue
下面講講本文主角,performance
,不是Chrome
開發者工具的Performance
面板,固然Chrome
開發者工具也能作性能監控。java
performance
是前端性能監控的API
,能夠獲取到當前頁面中也與性能相關的信息。git
咱們來看看天貓商城首頁經過window.performance
這個API
,獲取到的一些信息: github
window.performance
是一個對象,包含了四個屬性:
memory
、
navigation
、
timeOrigin
、
timing
,以及一個事件處理程序
onresourcetimingbufferfull
,咱們再來看看這幾個分別表明什麼?
在Chrome
中添加的一個非標準擴展,memory
這個屬性提供了一個能夠獲取到基本內存使用狀況的對象MemoryInfo
chrome
performance.memory = {
jsHeapSizeLimit, // 內存大小限制,單位是字節B
totalJSHeapSize, // 可以使用的內存大小,單位是字節B
usedJSHeapSize // JS對象佔用的內存大小,單位是字節B
}
複製代碼
若是usedJSHeapSize
的值大於totalJSHeapSize
,會出現內存泄露的問題,因此不能大於totalJSHeapSize
的值。後端
返回PerformanceNavigation
對象,提供了在指定的時間段發生的操做相關信息,包括頁面是加載仍是刷新、發生了多少重定向等。
performance.navigation = {
redirectCount: xxx,
type: xxx
}
複製代碼
PerformanceNavigation
對象有兩個屬性,redirectCount
和type
。
只讀屬性,表示達到這個頁面以前通過多少次重定向跳轉,可是這個接口有同源策略的限制,僅能檢測到同源的重定向。
只讀屬性,有四個返回值:0,1,2,225:
url
中直接輸入地址,至關於常數performance.navigation.TYPE_NAVIGATE
過Location.reload()
方法顯示的頁面,至關於常數performance.navigation.TYPE_RELOAD
performance.navigation.TYPE_BACK_FORWARD
performance.navigation.TYPE_RESERVED
返回性能測量開始的時間的高精度時間戳。
上面的時間就表示開始性能測試的時間。 一個回調的EventTarget
,當觸發resourcetimingbufferfull
事件的時候會被調用。
返回PerformanceTiming
對象,包含了各類與瀏覽器性能相關的數據,提供了瀏覽器處理頁面的各個階段的耗時,其總體結構能夠參考下圖:
PerformanceTiming
對象中的屬性都是隻讀屬性,值都是精確到
Unix
毫秒的時間戳:
返回當前瀏覽器窗口的前一個頁面的關閉,發生unload
事件時的時間戳。若是沒有前一個頁面,則等於fetchStart
屬性。
返回若是前一個頁面與當前頁面同域,則返回前一個頁面unload
事件發生時的時間戳。若是沒有沒有前一個頁面,或者以前的頁面跳轉不是在同一個域名內,則返回值爲0
和unloadEventStart
相對應,返回前一個頁面unload
事件綁定的回調函數執行完畢的時間戳。若是沒有沒有前一個頁面,或者以前的頁面跳轉不是在同一個域名內,則返回值爲0
返回第一個http
重定向發生時的時間戳。有跳轉而且是同域名內的重定向,不然返回值爲0
返回最後一個http
重定向完成時的時間戳。有跳轉而且是同域名內的重定向,不然返回值爲0
返回瀏覽器準備好使用http
請求抓取文檔的時間戳,這發生在檢查本地緩存以前
返回DNS
域名查詢開始的時間戳,若是使用了本地緩存(也就是沒有作DNS
查詢,直接從緩存中取到IP
)或者使用了持久鏈接,則與fetchStart
值相等
返回DNS
域名查詢完成的時間戳,若是使用了本地緩存(也就是沒有作DNS
查詢,直接從緩存中取到IP
)或者使用了持久鏈接,則與fetchStart
值相等
返回http
(TCP
)開始創建鏈接的時間戳,若是是持久鏈接,則與fetchStart
值相等。若是在傳輸層發生了錯誤而且從新創建鏈接,則這裏顯示的是新創建的鏈接開始的時間戳
返回http
(TCP
)完成創建鏈接的時間戳,完成了四次握手,若是是持久鏈接,則與fetchStar
t值相等。若是在傳輸層發生了錯誤而且從新創建鏈接,則這裏顯示的是新創建的鏈接完成的時間戳。鏈接創建指的是全部握手和認證過程所有結束
返回https
鏈接開始的時間戳,若是不是安全鏈接,不然返回值爲0
返回http
請求讀取真實文檔開始的時間戳(完成創建鏈接),包括從本地讀取緩存。若是鏈接錯誤重連時,這裏顯示的也是新創建鏈接的時間戳
返回http
開始接收響應的時間戳(獲取到第一個字節),包括從本地讀取緩存
返回http
響應所有接收完成的時間戳(獲取到最後一個字節),包括從本地讀取緩存
返回開始解析渲染DOM
樹的時間戳,此時Document.readyState
變爲loading
,並將拋出readystatechange
相關事件
返回完成解析DOM
樹的時間戳,Document.readyState
變爲interactive
,並將拋出readystatechange
相關事件。這裏只是DOM
樹解析完成,這時候並無開始加載網頁內的資源
返回DOM
解析完成後,網頁內資源加載開始的時間戳。即全部須要被執行的腳本開始被解析了。在DOMContentLoaded
事件拋出前發生
返回DOM
解析完成後,網頁內資源加載完成的時間戳。例如JS
腳本加載執行完成,不論執行順序。DOMContentLoaded
事件也已經完成
返回DOM
解析完成,且資源也準備就緒的時間戳。Document.readyState
變爲complete
,並將拋出readystatechange
相關事件
返回load
事件發送給文檔,load
回調函數開始執行的時間戳。若是沒有綁定load
事件,返回值爲0
返回load
事件的回調函數執行完畢的時間戳。若是沒有綁定load
事件,返回值爲0
上面已經解釋了相關屬性的含義,經過上面的數據能作不少幫助咱們作性能監控的事情:
let performance = window.performance;
let t = performance.timing;
let time = t.loadEventEnd - t.navigationStart;
複製代碼
DOM
樹嵌套狀況let performance = window.performance;
let t = performance.timing;
let time = t.domComplete - t.responseEnd;
複製代碼
let performance = window.performance;
let t = performance.timing;
let time = t.redirectEnd - t.redirectStart;
複製代碼
DNS
查詢時間:可作預加載,緩存,減小查詢時間let performance = window.performance;
let t = performance.timing;
let time = t.domainLookupEnd - t.domainLookupStart;
複製代碼
let performance = window.performance;
let t = performance.timing;
let time = t.responseStart - t.navigationStart;
複製代碼
let performance = window.performance;
let t = performance.timing;
let time = t.responseEnd - t.requestStart;
複製代碼
onload
回調函數的時間let performance = window.performance;
let t = performance.timing;
let time = t.loadEventEnd - t.loadEventStart;
複製代碼
DNS
緩存時間let performance = window.performance;
let t = performance.timing;
let time = t.domainLookupStart - t.fetchStart;
複製代碼
let performance = window.performance;
let t = performance.timing;
let time = t.unloadEventEnd - t.unloadEventStart;
複製代碼
TCP
創建鏈接完成握手的時間let performance = window.performance;
let t = performance.timing;
let time = t.connectEnd - t.connectStart;
複製代碼
咱們能夠計算出頁面加載的過程當中各階段的耗時,根據時長判斷某階段的性能怎麼樣,再作進一步的優化處理。這是性能優化很重要的一步,咱們須要定位到具體的哪一個階段耗時過長,對症下藥。
performance
也提供了一些方法,咱們來看看一些經常使用的方法:
建立一個DOMHighResTimeStamp
保存在資源緩存數據中,可經過performance.getEntries()
等相關接口獲取。簡單的理解就是能夠作標記,也就是「打點」
performance.mark(name);
複製代碼
用於清除標記,若是不加參數,就表示清除全部標記。
performance.clearMarks(name); // 清除指定標記
performance.clearMarks(); // 清除全部標記
複製代碼
計算兩個mark
之間的時長,建立一個DOMHighResTimeStamp
保存在資源緩存數據中,可經過performance.getEntries()
等相關接口獲取。
performance.measure(name, startMark, endMark);
複製代碼
移除緩存中全部entryType
爲measure
的資源數據。
performance.clearMeasures(name); // 清除指定記錄間隔數據
performance.clearMeasures(); // 清除全部記錄間隔數據
複製代碼
上面的四個API
,能夠自定義統計一些數據,例如統計某函數的執行時間。
在Vue
中也有用到,爲了追蹤組件的性能,在Vue2.X
中全局配置API
有這麼個方法:
Vue.config.performance = false;
複製代碼
設置爲true
以在瀏覽器開發工具的性能/時間線面板中啓用對組件初始化、編譯、渲染和打補丁的性能追蹤。只適用於開發模式和支持performance.mark API
的瀏覽器上。咱們入口文件中開啓,開啓後可使用Vue Performance Devtool
這個chrom
e插件來查看各組件加載狀況:
if (process.env.NODE_ENV !== 'production') {
Vue.config.performance = true;
}
複製代碼
在
Vue
源碼中,也是經過
performance.mark
和
performance.measure
來實現的,咱們看下具體的源碼實現:
// vue/src/core/util/perf.js
import { inBrowser } from './env'
export let mark
export let measure
if (process.env.NODE_ENV !== 'production') { // 環境判斷 開發環境執行下面程序
const perf = inBrowser && window.performance // 瀏覽器環境
/* istanbul ignore if */
if (
perf &&
perf.mark &&
perf.measure &&
perf.clearMarks &&
perf.clearMeasures
) {
mark = tag => perf.mark(tag) // 給定tag打點,作標記
measure = (name, startTag, endTag) => {
perf.measure(name, startTag, endTag) // 計算兩個mark之間的時長
perf.clearMarks(startTag) // 清除startTag標記
perf.clearMarks(endTag) // 清除endTag標記
// perf.clearMeasures(name) // 清除指定記錄間隔數據
}
}
}
複製代碼
從上面的代碼能夠看出,尤大大經過mark
和measure
兩個函數對performance.mark()
和performance.measure()
進行了封裝。咱們再來看看在源碼中怎麼應用的:
// vue/src/core/instance/init.js
import { mark, measure } from '../util/perf'
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag) // 開始標記
}
// .... 中間代碼省略
// 一系列初始化函數
// initLifecycle(vm)
// ....
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag) // 結束標記
measure(`vue ${vm._name} init`, startTag, endTag) // 計算兩個mark之間的時長
}
}
}
複製代碼
上面是Vue
初始化實現的相關程序,上面的打點標記就是爲了追蹤組件初始化的性能狀況,就是在初始化的代碼的開頭和結尾分別使用mark
函數打上兩個標記,而後經過measure
函數對這兩個標記點進行性能計算。在編譯、渲染和打補丁也作了性能追蹤,感興趣的同窗能夠看看。
上面是Vue
中對performance
的應用,起初看vue
源碼的時候看到這塊不是很明白,不影響總體的閱讀,沒有太關係,最近工做上有用到performance
,想到Vue
好像也用到了,就回頭看看Vue
中的應用,如今是整明白了。咱們再來看看一個比較簡單實例,更好的理解下:
performance.mark('markStart'); // 標記一個開始點
for(let i = 0; i < 10; i++) {
console.log(i);
}
performance.mark('markEnd'); // 標記一個結束點
performance.measure('measureVal', 'markStart', 'markEnd');
let measures = performance.getEntriesByName('measureVal');
let measure = measures[0]
console.log('milliseconds: ', measure.duartion); // 0.8999999990919605
// 清除標記
performance.clearMarks();
performance.clearMeasures();
複製代碼
上面就能夠經過提供的API
計算出for循環
的執行時間。這幾個API
在性能監控應用比較頻繁。
獲取全部資源請求的時間數據,這個函數返回一個按startTime
排序的對象數組:
從上面能夠看出,返回都是資源頁面加載的相關數據,不少屬性與performance.timing
同樣,在這就再也不解釋了。在這裏梳理其餘幾個重要的屬性:
name
: 資源名稱,是資源的絕對路徑或調用mark方法自定義的名稱(例如entryType
爲resource
時,name表示資源的路徑)。duration
,一個DOMHighResTimeStamp
對象,獲取該資源消耗時長。startTime
,一個DOMHighResTimeStamp
對象,開始獲取該資源的時間。entryType
:值 | 該類型對象 | 描述 |
---|---|---|
mark |
PerformanceMark |
經過mark() 方法添加到數組中的對象 |
measure |
PerformanceMeasure |
經過measure() 方法添加到數組中的對象 |
paint |
PerformancePaintTiming |
值爲first-paint 首次繪製、first-contentful-paint 首次內容繪製 |
resource |
PerformanceResourceTiming |
全部資源加載時間,用處最多 |
navigation |
PerformanceNavigationTiming |
現除chrome 和Opera 外均不支持,導航相關信息 |
frame |
PerformanceFrameTiming |
現瀏覽器均未支持 |
initiatorType
,初始化該資源的資源類型:發起對象 | 值 | 描述 |
---|---|---|
a Element |
link /script /img /iframe 等 |
經過標籤形式加載的資源,值是該節點名的小寫形式 |
a CSS resourc |
css |
經過css 樣式加載的資源,好比background 的url 方式加載資源 |
a XMLHttpRequest object |
xmlhttprequest /fetch |
經過xhr 加載的資源 |
a PerformanceNavigationTiming object |
navigation |
當對象是PerformanceNavigationTiming 時返回 |
根據參數name
,type
獲取一組當前頁面已經加載的資源數據。name
的取值對應到資源數據中的name
字段,type
取值對應到資源數據中的entryType
字段。
let entries = window.performance.getEntriesByName(name, type);
複製代碼
根據參數type
獲取一組當前頁面已經加載的資源數據。type
取值對應到資源數據中的entryType
字段
let entries = window.performance.getEntriesByType(type);
複製代碼
getEntriesByName
和getEntriesByType
能夠經過指定參數獲取某類型的資源數據,這樣咱們能夠資源數據分類統計,得出各種數據的狀況。
上面就是performance
的主要內容了,能夠經過這個API
作不少關於性能監控的事情,須要根據本身具體的場景制定合適的方案。熟悉Chrome
開發者工具的同窗,也是能夠經過performance
面板來作性能監控,那爲啥還提供API
來作呢,其實更多的但願經過打點統計數據量,能夠經過可視化的方式對數據分析,更直觀,進而作下一步操做。
文章若有不正確的地方歡迎各位大佬指正,也但願有幸看到文章的同窗也有收穫,一塊兒成長!
——本文首發於我的公衆號———
最後,歡迎你們關注個人公衆號,一塊兒學習交流。