? 性能監(jiān)控 在前端一直是一個口頭上備受關(guān)注但開發(fā)中又常被忽略的點纲熏,畢竟不是每個開發(fā)者很容易就做到的事帝美。好在HTML5新增了performance
特性鲤遥,它是High Resolution Time API 的一部分咙轩,目的在于獲取到當前頁面中與性能相關(guān)的信息愤惰,以便幫助開發(fā)者直觀感受頁面性能及針對問題優(yōu)化荸哟。
? 了解如何監(jiān)控頁面性能前嘱能,我們先回顧幾個指標:
(1)白屏?xí)r間:頁面被打開擎浴,到首字節(jié)渲染呈現(xiàn)所需的時間滥朱。
(2)首屏?xí)r間:首屏內(nèi)容渲染完成所需的時間根暑。
(3)下載時間(HTTP請求耗時):頁面所需資源從服務(wù)器上下載完成所需的時間。
(4)DOM樹解析時間:資源下載完成到頁面構(gòu)建展示出來所需的時間徙邻。
? ...
? 這些信息都如何獲扰畔印?在此標準之前鹃栽,也有一些手段可以實現(xiàn)躏率,但H5的performance
直接來源于瀏覽器躯畴,與手工Date.time,Cookie等對比薇芝,使用上更方便蓬抄,數(shù)據(jù)上更準確。(Date.now()
會受程序阻塞影響)
?
$ Performance 屬性
? 關(guān)于performance屬性夯到,建議讀者自己在工具編輯器上直接打印出來看看更能真切的體會該接口嚷缭。本文主要介紹前兩者,對其他內(nèi)容感興趣的同學(xué)耍贾,可以 戳這里
-
.timing
(只讀)
:對象阅爽;包含了延遲相關(guān)的性能信息。 -
.navigation
(只讀)
:對象荐开;包含了指定的時間段里發(fā)生的操作相關(guān)信息付翁,包括頁面是加載還是刷新、發(fā)生了多少次重定向等等晃听。 -
.timeOrigin
(只讀)
:即將失效百侧。用于返回性能測量開始時的高精度時間戳。 -
.memory:由chrome拓展的非標準屬性能扒,用于返回基本內(nèi)存的使用情況佣渴。注意非chrome不支持。
?
# Performance.timing 只讀
const PerformanceTiming = window.performance.timing
? 返回值為一個對象初斑,記錄著完整的頁面加載信息辛润。其各個節(jié)點如下:
? 看著上圖,回顧一下一般意義的頁面加載過程:瀏覽器向服務(wù)器請求資源
--> DOM結(jié)構(gòu)解析
--> 構(gòu)建DOM樹
--> 構(gòu)建CSS規(guī)則樹
--> 構(gòu)建渲染樹
--> 繪制頁面
见秤∩笆可以看出,這個過程只是上圖中的某一小部分鹃答,我們來詳談一下實際的整個過程
-
Prompt for unload 階段
-
.navigationStart
:瀏覽器完成卸載前一個文檔的時間晦溪。如果沒前一個文檔,則該值與第三步.fetchStart
的值相同挣跋。 -
.unloadEventStart
:返回前一個同源文檔出發(fā)卸載(unload)事件前的時間三圆。如果沒有前一個文檔,或前文檔與本文檔不同源避咆,或需重定向舟肉,則返回0。 -
.unloadEventEnd
:返回前一個同源文檔完成卸載的時間查库。如果沒有或文檔不同源路媚,則返回0.
-
-
Redirect 階段
-
.redirectStart
:http重定向開始的時間。如果中間有多個重定向樊销,且每個重定向均同源整慎,則返回第一個重定向的.fetchStart
時間脏款,若不同源,則為0 -
.redirectEnd
:http重定向結(jié)束時間裤园。如果中間有多個重定向且均同源撤师,則返回最后一個重定向結(jié)束時間。若不同源拧揽,則為0剃盾。
-
-
App cache 階段
-
.fetchStart
:瀏覽器準備好使用HTTP請求來獲取(fetch
)文檔的時間,這個時間會在檢查任何應(yīng)用緩存之前淤袜。
-
-
DNS查詢階段
-
.domainLookupStart
:用戶代理對當前文檔所屬域進行DNS查詢開始的時間痒谴。如果是長連接(如websocket
),或本地緩存了铡羡,則該值與.fetchStart
相同 -
.domainLookupEnd
:域名查詢結(jié)束的時間积蔚。如果是長連接,或本地緩存了烦周,則該值與.fetchStart
相同
-
-
TCP連接階段
-
.connectStart
:用戶代理開始向服務(wù)器請求所需文檔時库倘,連接建立的開始時間。如果是長連接论矾,或本地緩存了,則該值與.fetchStart
相同 -
.secureConnectStart
:返回與服務(wù)器開始SSL握手時的時間杆勇。異常情況同上贪壳。 -
.connectEnd
: HTTP握手成功,認證結(jié)束蚜退,連接建立時的時間闰靴。如果是長連接,或本地緩存了钻注,則該值與.fetchStart
相同蚂且。
-
-
Request 階段
-
requestStart
:從服務(wù)器/緩存/本地資源中開始請求文檔的時間。如果連接發(fā)生斷開重連幅恋,該信息會被刷新杏死。 - 沒有請求結(jié)束時間是因為該動作發(fā)生在服務(wù)器端,且受數(shù)據(jù)鏈路等各個因素影響捆交,瀏覽器并不能準確反饋該信息
-
-
Response 階段
-
.responseStart
:從服務(wù)器/緩存/本地資源中接收到第一個字節(jié)時的時間淑翼。如果連接發(fā)生斷開重連,該信息會被刷新品追。 -
.responseEnd
:從服務(wù)器/緩存/本地資源中接收到最后一個字節(jié)時的時間玄括。如果連接提前關(guān)閉,則返回提前關(guān)閉的時間肉瓦。獲取該值時需注意要在Response結(jié)束之后遭京,如window.onload
胃惜,否則可能不準確。
-
-
Processing 執(zhí)行階段
-
.domLoading
:資源下載完成哪雕,開始解析DOM結(jié)構(gòu)船殉,當Document.readyState
的值更新為loading
時的時間。 -
.domInteractive
:DOM解析完成热监,開始加載內(nèi)嵌資源捺弦,即Document.readyState
的值更新為interactive
時的時間 - 執(zhí)行階段內(nèi)的 DOMContentLoaded 階段
-
.domContentLoadedEventStart
:解析器發(fā)送DOMContentLoaded
事件,所有需要被執(zhí)行的腳本均解析完成時的時間孝扛。 -
.domContentLoadedEventEnd
:所有立即執(zhí)行的腳本均執(zhí)行完成時的時間列吼。不執(zhí)行的腳本如懶加載資源不在該范圍內(nèi)。
-
-
.domComplete
:當前文檔解析完成苦始,document.readyState
的值更新為complete
時的時間寞钥。
-
-
load 業(yè)務(wù)涉入階段
-
.loadEventStart
:文檔觸發(fā)load事件的時間,如果還沒觸發(fā)陌选,則返回0理郑。 -
.loadEventEnd
:文檔結(jié)束load事件的時間,未觸發(fā)則返回0咨油。
?
-
# 性能監(jiān)控指標
? 通過以上的各個事件分析您炉,不難得出如下各個時間段:
const timing = window.performance.timing
-
DNS解析耗時:
timing.domainLookupEnd - timing.domainLookupStart
-
TCP連接耗時:
timing.connectEnd - timing.connectStart
-
發(fā)送請求耗時:
timing.responseStart - timing.requestStart
-
接收請求耗時:
timing.responseEnd - timing.responseStart
-
解析DOM耗時:
timing.domInteractive - timing.domLoading
-
頁面加載完成:
timing.domContentLoadedEventStart - timing.domInteractive
-
DOMContentLoaded事件耗時:
timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart
-
DOM加載完成:
timing.domComplete - timing.domContentLoadedEventEnd
-
DOMLoad事件耗時:
timing.loadEventEnd - timing.loadEventStart
? 除此之外,在文首提到的其他幾個性能指標役电,如下:
-
白屏?xí)r間:
timing.responseStart - timing.navigationStart
-
首屏?xí)r間:
timing.domComplete- timing.navigationStart
-
資源下載總耗時:
timing.responseEnd - timing.requestStart;
-
請求完畢至DOM加載:
timing.domInteractive - timing.responseEnd
?
# 實戰(zhàn)案例
? 封裝一個函數(shù)如下赚爵,注釋前半部為參數(shù)功能,后半部為監(jiān)控到頁面性能問題時可能的原因
function getPerformanceTiming () {
var performance = window.performance
// 瀏覽器兼容性考慮
if(!performance) {
console.log('您的瀏覽器不支持 performance 接口')
return
}
const t = performance.timing
let times = {}
// 頁面加載完成時間 - 用戶需等待頁面可用時間
times.loadPage = t.loadEventEnd - t.navigationStart
// 解析dom樹結(jié)構(gòu)時間 - DOM樹嵌套不宜太深
times.domReady = t.domeComplete - t.responseEnd
// 重定向時間 - 若拒絕重定向法瑟,檢查是否有類似‘http://example.com/’寫成‘http://example.com’錯誤
times.redirect = t.redirectEnd - redirectStart
// DNS解析時間 - 可增加DNS預(yù)加載冀膝。頁面涉及域名是否過多
times.lookupDomian = t.domainLookupEnd - t.domainLookupStart
// 首字節(jié)響應(yīng)時間 - 數(shù)據(jù)鏈路的響應(yīng)速度,受機房霎挟,CDN窝剖,帶寬,服務(wù)器性能等影響
times.ttfb = t.responseStart - t.navigationStart
// 資源加載完成時間 - Nginx上配置gzip壓縮減少下載資源
times.request = t.responseEnd - t.reuqestStart
// onload執(zhí)行效率 - 避免過多邏輯在onload中執(zhí)行酥夭,考慮資源懶加載赐纱,延遲獲取等
times.loadEvent = t.loadEventEnd - t.loadEventStart
// DNS緩存時間
times.appcache = t.domianLookupStart - t.fetchStart
// 卸載頁面時間
times.unloadEvent = t.unloadEventEnd - t.unloadEventStart
// TCP連接建立及完成握手時間
times.connect = t.connectEnd - t.connectStart
return times
}
?
# Performance.navigation 只讀
? .navigation
返回一個performanceNavigation對象,提供了在指定的時間段里發(fā)生的操作和相關(guān)信息熬北,包括頁面是加載千所、刷新還是重定向。
const navigation = window.performance.navigation
? 該對象返回值信息如下
-
頁面載入類型 -
type
-
0
:同TYPE_NAVIGATE
蒜埋;如點擊鏈接淫痰,url輸入,腳本執(zhí)行跳轉(zhuǎn)整份,或書簽和表單的提交等方式載入 -
1
:同TYPE_RELOAD
待错;如點擊刷新頁面按鈕籽孙,或腳本Location.reload()
載入 -
2
:同TYPE_BACK_FORWARD
;通過歷史記錄的前進和后退進入 -
255
:同TYPE_RESERVED
火俄;通過其他方式進入
-
- 重定向次數(shù) -
redirectCount
-
序列化方法 -
toJson()
鏈式調(diào)用辦法犯建,將PerformanceNavigation轉(zhuǎn)化為JSON對象。
?
$ Performance 方法
? timing
屬性主要針對文檔載入及之前的各個節(jié)點性能監(jiān)控瓜客,無法落實到其他業(yè)務(wù)邏輯執(zhí)行适瓦。想要監(jiān)控更多信息,就需要使用Performance接口提供的方法來實現(xiàn)谱仪。
# now() (單位ms)
? performance.now()
方法返回了相對于 performance.timing.navigationStart
(頁面初始化) 的時間玻熙,而Date.now()
返回的是UNIX時間也就是距1970年的時間。且因為performance.now()
的時間是以一定速率慢慢增加的疯攒,不受系統(tǒng)時間影響嗦随,也不受進程阻塞影響,比Date.now()
時間來的更精準一些敬尺。
let t0 = window.performance.now();
todo()
let t1 = window.performance.now();
console.log("todo執(zhí)行時間:"枚尼, (t1 - t0) + "毫秒.")
# getEntries()
? 返回一個按startTime
排序的數(shù)組,包含加載本頁面所有的資源請求相關(guān)時間數(shù)據(jù)的集合砂吞。為更好的理解看一個entry實例數(shù)據(jù)署恍,以訪問https://www.baidu.com/
為例:
const entries = window.performance.getEntries()
console.log(entries)
以下為返回數(shù)組的第一項:
? 可以發(fā)現(xiàn),整個 Performance.timing 的數(shù)據(jù)節(jié)點均已包含蜻直。除此之外盯质,還包括了以下幾個信息:
-
name:資源名稱。是資源的絕對路徑或
mark()
方法自定義的名稱袭蝗。 - startTime:開始時間
- duration:加載時間
- entryType:資源類型;詳情如下
- initiatorType:請求發(fā)起者般婆;詳情如下
entryType的值
值 | 描述 |
---|---|
mark |
通過mark()添加到數(shù)組中的對象 |
measure |
通過measure()添加到數(shù)組中的對象 |
resource |
所有資源加載時間(重要) |
navigation |
導(dǎo)航相關(guān)信息到腥,僅chrome和Opera支持 |
frame |
- |
server |
- |
initiatorType的值
值 | 發(fā)起對象 | 描述 |
---|---|---|
link/script/img/iframe 等 |
某個標簽元素 | 標簽形式加載 |
css |
某個css樣式 | 通過css樣式加載,如background 的url() 資源 |
xmlhttprequest |
某個http請求 | 通過xhr加載的資源 |
navigation |
某個performanceNavigation對象 | 當對象是PerformanceNavigationTiming時返回 |
? 因此蔚袍,我們獲取其性能時間數(shù)據(jù)可封裝函數(shù)如下
// 計算加載時間
function getEntryTiming (entry) {
var t = entry;
var times = {};
// 重定向的時間
times.redirect = t.redirectEnd - t.redirectStart;
// DNS 查詢時間
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;
// 內(nèi)容加載完成的時間
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;
}
// run it
var entries = window.performance.getEntries();
entries.forEach(function (entry) {
var times = getEntryTiming(entry);
console.log(times);
});
? 執(zhí)行該方法會發(fā)現(xiàn)乡范,一個全量的entries
存在了過多的干擾信息,如果要從中挑出某些有用項進行比較只能通過數(shù)組過濾手段實現(xiàn)比較麻煩啤咽,好在performance接口提供了這個方法
# getEntriesByType()
? performance.getEntriesByType()
方法返回給定類型的entries
數(shù)組集合晋辆,其本質(zhì)就是在全量數(shù)據(jù)中按entryType
屬性過濾,返回過濾后的數(shù)據(jù)宇整,效果等同于Array.filter()
瓶佳。該方法常配合mark()
方法使用,用來獲取用戶自己打的標簽數(shù)據(jù)鳞青。
entries = window.performance.getEntriesByType(type);
# getEntriesByName()
? 使用辦法同getEntriesByType()
霸饲,接受一個參數(shù)为朋,用于指定entries
名稱『衤觯可以用來統(tǒng)計某一個函數(shù)被執(zhí)行的次數(shù)及各個執(zhí)行時刻习寸,另一個更重要的是用來檢索measure測量的duration耗時。
?
# mark()
? 使用performance.mark()
也可以精準的計算程序的執(zhí)行時間傻工。思路就是在某些關(guān)鍵位置插入一些標記霞溪,當程序運行到標記處時,Performance會入棧一個entry
中捆。這樣鸯匹,通過在需要分析性能的邏輯段落前后插入不同的標記,來實現(xiàn)對該處性能的監(jiān)控轨香。
function markSample(name) {
const markStart = name + '_markStart'
const markEnd= name + '_markEnd'
window.performance.mark(markStart)
for(let i = 0; i < 100; i++) {
for(let j = 0; j < 100; j++) {
// TODO:
}
}
window.performance.mark(markEnd)
}
// run it
markSample(‘first’)
const marks = window.performance.getEntriesByType('mark')
console.log(marks)
執(zhí)行結(jié)果會包含四個關(guān)鍵屬性忽你,如下:
# measure()
? performance.measure()
用于測量兩個標記之間執(zhí)行的時間,并把它賦值給第一個參數(shù)(measure名稱)上臂容。如在上例的markSample
函數(shù)底部插入一下代碼
window.performance.measure('measure_test', markStart, markEnd)
var measureTest= window.performance.getEntriesByName('measure_test');
console.log(measureTest);
?
? 值得關(guān)注的是科雳,由于標記在插入后,每次程序執(zhí)行到此處將入棧一個entry
脓杉,而該數(shù)據(jù)是記錄在全局的window
下的糟秘,因此當標記過多或被執(zhí)行次數(shù)太多時,可能出現(xiàn)內(nèi)存污染等問題球散,因此尿赚,這就要求在標記使用結(jié)束后及時清除他們。
# clearMarks()
? performance.clearMarks()
接受 0/1 個參數(shù)蕉堰,表示將要清除的標記名稱
// 指定清除某個標記
window.performance.clearMarks('first_markStart')
// 清除所有標記
window.performance.clearMarks()
# clearMeasures()
? 測量完成后也應(yīng)當及時清除凌净,用法:
// 清除指定測量
window.performance.clearMeasures('first_measure');
// 清除所有測量
window.performance.clearMeasures();
?
$ 使用mark測量timing事件
? 可能有個錯誤的理解就是performance.measure()
只能測量performance.mark()
的標記,其實不然屋讶,比如冰寻,在timing中,我們是這么測量domReady事件的:
cosnt t = performance.timing
const domReady = t.domComplete - t.responseEnd;
console.log(domReady )
也可以使用measure()
來實現(xiàn)如下:
window.performance.measure('domReady','responseEnd' , 'domComplete');
var domReadyMeasure = window.performance.getEntriesByName('domReady');
console.log(domReadyMeasure);
?
$ refs
參考文獻
performance - MDN
HTML5 performance API 草案.
初探performance - AlloyTeam