最近有個需求是監(jiān)測性能,要求在移動端項目里統(tǒng)計控制臺(如下圖)最后一行的Finish甚垦,DOMContentLoaded和Load三個時間茶鹃。
一、指標解釋
(1)Finish
Finish 時間是頁面上所有 http 請求發(fā)送到響應(yīng)完成的時間制轰,HTTP1.0/1.1 協(xié)議限定前计,單個域名的請求并發(fā)量是 6 個,即Finish是所有請求(不只是XHR請求垃杖,還包括DOC男杈,img,js调俘,css等資源的請求)在并發(fā)量為6的限制下完成的時間伶棒。
頁面發(fā)送請求和頁面解析文檔結(jié)構(gòu),分屬兩個不同的線程彩库,所以 Finish 時間與DOMContentLoaded 和 Load 并無直接關(guān)系肤无。
Finish 的時間比 Load 大,意味著頁面有相當(dāng)部分的請求量骇钦,F(xiàn)inish 的時間比 Load 小宛渐,意味著頁面請求量很少,如果頁面是只有一個 html文檔請求的靜態(tài)頁面眯搭,F(xiàn)inish時間基本就等于HTML文檔請求的時間窥翩。
(2)DOMContentLoaded
- 對應(yīng)頁面DOMContentLoaded事件觸發(fā)的時間點:DOM樹構(gòu)建完成。即HTML頁面由上向下解析HTML結(jié)構(gòu)到末尾封閉標簽</html> 鳞仙。
(3)Load
- 對應(yīng)頁面Load事件觸發(fā)的時間點:頁面加載完畢寇蚊。 DOM樹構(gòu)建完成后,繼續(xù)加載html/css 中的圖片資源等外部資源棍好,加載完成后視為頁面加載完畢仗岸。
二、http請求過程及網(wǎng)頁渲染原理
(1)http請求過程
(2)渲染原理
當(dāng)我們在瀏覽器地址輸入URL時借笙,瀏覽器會發(fā)送請求到服務(wù)器扒怖,服務(wù)器將請求的HTML文檔發(fā)送回瀏覽器,瀏覽器將文檔下載下來后业稼,便開始從上到下解析盗痒,解析完成之后,會生成DOM盼忌。如果頁面中有css积糯,會根據(jù)css的內(nèi)容形成CSSOM,然后DOM和CSSOM會生成一個渲染樹谦纱,最后瀏覽器會根據(jù)渲染樹的內(nèi)容計算出各個節(jié)點在頁面中的確切大小和位置看成,并將其繪制在瀏覽器上。
html的解析又會被js打斷跨嘉,解析過程中遇到<script>標簽的時候川慌,便會停止解析過程,轉(zhuǎn)而去處理腳本祠乃,如果腳本是內(nèi)聯(lián)的梦重,瀏覽器會先去執(zhí)行這段內(nèi)聯(lián)的腳本,如果是外鏈的亮瓷,那么先會去加載腳本琴拧,然后執(zhí)行。在處理完腳本之后嘱支,瀏覽器便繼續(xù)解析HTML文檔蚓胸。(所以一般js文件放到最后面)
而在現(xiàn)在瀏覽器中,為了減緩渲染被阻塞的情況除师,現(xiàn)代的瀏覽器都使用了猜測預(yù)加載沛膳。當(dāng)解析被阻塞的時候,瀏覽器會有一個輕量級的HTML(或CSS)掃描器(scanner)繼續(xù)在文檔中掃描汛聚,查找那些將來可能能夠用到的資源文件的url锹安,在渲染器使用它們之前將其下載下來,并且下載是可以并行進行的倚舀,并行的上限一般為6叹哭。
三、Performance API
主要用到的是performance.timing對象瞄桨,具體解釋見上面的鏈接话速,下面這張圖對應(yīng)各個指標的時間點。
參考這段代碼的統(tǒng)計指標
(function performanceStatistics(){
var performance = window.performance;
if (!performance) {
// 當(dāng)前瀏覽器不支持
console.log('你的瀏覽器不支持 performance 接口');
return ;
}
var timing = performance.timing;
// 如果我們需要盡量對頁面加載周期的數(shù)據(jù)進行詳細的統(tǒng)計分析:
console.log('統(tǒng)計模塊性能時間:'); // 寫出具體模塊名稱
console.log('準備新頁面時間耗時: ' + (timing.fetchStart - timing.navigationStart) + 'ms');
console.log('Appcache 耗時: ' + (timing.domainLookupStart - timing.fetchStart)+ 'ms');
console.log('DNS 查詢耗時: ' + (timing.domainLookupEnd - timing.domainLookupStart)+ 'ms');
console.log('TCP連接耗時: ' + (timing.connectEnd - timing.connectStart)+ 'ms');
console.log('request請求耗時: ' + (timing.responseEnd - timing.requestStart)+ 'ms');
console.log('請求完畢至DOM加載: ' + (timing.domInteractive - timing.responseEnd)+ 'ms');
console.log('解釋dom樹耗時: ' + ( timing.domComplete - timing.domInteractive)+ 'ms');
console.log('load事件耗時: ' + ( timing.loadEventEnd - timing.loadEventStart)+ 'ms');
console.log('從開始至load完成: ' + ( timing.loadEventEnd - timing.navigationStart)+ 'ms');
console.log('頁面加載耗時: ' + ( timing.loadEventStart - timing.navigationStart)+ 'ms');
// 至此芯侥,我們可以將頁面加載過程中的相關(guān)耗時詳盡的統(tǒng)計輸出泊交,分析耗時較長的地方并作出相關(guān)的優(yōu)化。
})()
因為是持久鏈接柱查,所以domainLookupStart廓俭、domainLookupEnd、connectEnd都等于fetchStart唉工,所以Appcache 耗時研乒、DNS 查詢耗時、TCP連接耗時都是0淋硝。
最后整出來3個指標:第一個對應(yīng)控制臺的DOMContentLoaded雹熬,第三個對應(yīng)控制臺的Load
DOM加載時間(timing.domContentLoadedEventStart - timing.navigationStart)
請求時間(timing.responseEnd - timing.requestStart)
頁面加載時間(timing.loadEventStart - timing.navigationStart)
統(tǒng)計代碼如下:
function performance(){
var performance = window.performance;
if (!performance) {return ;}
var path = window.location.pathname.replace(/(\d+)/g, '') ,
timing = performance.timing,
DOMLoaded = timing.domContentLoadedEventStart - timing.navigationStart ,
requestTime = timing.responseEnd - timing.requestStart ,
pageLoaded = timing.loadEventStart - timing.navigationStart ;
_czc.push(['_trackEvent', 'DOMLoaded-time', 'show', path , DOMLoaded, '']) ;
_czc.push(['_trackEvent', 'requestTime-time', 'show',path, requestTime, '']) ;
_czc.push(['_trackEvent', 'pageLoaded-time', 'show', path, pageLoaded, '']) ;
}
四宽菜、監(jiān)測代碼加在哪兒
這個是個坑,因為項目采用的是nuxt(vue)的竿报,單頁面铅乡,但是每個頁面都需要統(tǒng)計這些數(shù)據(jù),想寫在全局烈菌。試了好多方案阵幸,最后選了一種。
方案1:在mounted里寫
剛開始在首頁試的芽世,發(fā)現(xiàn)要寫在window.onload里才能統(tǒng)計到真實數(shù)據(jù)挚赊。因為是單頁面,就算在每個頁面里寫onload济瓢,取到的也都是一樣的數(shù)荠割。
方案2:中間件
這個可以寫在全局,但是路由跳轉(zhuǎn)的時候取不到window旺矾,設(shè)置延時或window.onload也不行涨共。
方案3:插件
寫了個js文件,nuxt.config.js配置了ssr: false宠漩,設(shè)置延時或window.onload還是不行举反。
方案4:nuxt.config.js加在script標簽里。這個方案是可行的扒吁,能檢測到第一次進入頁面時的數(shù)據(jù)或者是刷新當(dāng)前頁面的數(shù)據(jù)火鼻。
{ innerHTML: "window.onload = function(){var performance = window.performance;if (!performance) {return ;} var path = window.location.pathname.replace(/(\d+)/g, '') , timing = performance.timing, DOMLoaded = timing.domContentLoadedEventStart - timing.navigationStart , requestTime = timing.responseEnd - timing.requestStart , pageLoaded = timing.loadEventStart - timing.navigationStart ; _czc.push(['_trackEvent', 'DOMLoaded-time', 'show', path , DOMLoaded, '']) ; _czc.push(['_trackEvent', 'requestTime-time', 'show',path, requestTime, '']) ; _czc.push(['_trackEvent', 'pageLoaded-time', 'show', path, pageLoaded, '']);}"}
即:
window.onload = function(){
var performance = window.performance;
if (!performance) {return ;}
var path = window.location.pathname.replace(/(\\d+)/g, '') ,
timing = performance.timing,
DOMLoaded = timing.domContentLoadedEventStart - timing.navigationStart ,
requestTime = timing.responseEnd - timing.requestStart ,
pageLoaded = timing.loadEventStart - timing.navigationStart ;
_czc.push(['_trackEvent', 'DOMLoaded-time', 'show', path , DOMLoaded, '']) ;
_czc.push(['_trackEvent', 'requestTime-time', 'show',path, requestTime, '']) ;
_czc.push(['_trackEvent', 'pageLoaded-time', 'show', path, pageLoaded, '']) ;
}
遇到的問題:
1、var path = window.location.pathname.replace(/(\d+)/g, '') 這段代碼在瀏覽器控制臺能起效雕崩,但是統(tǒng)計數(shù)據(jù)里還是會帶著后面的參數(shù)id魁索。
發(fā)現(xiàn)是轉(zhuǎn)義了匹配上了字母d。盼铁。粗蔚。要加個轉(zhuǎn)義符\
2、統(tǒng)計數(shù)據(jù)并不全饶火,不是所有的頁面都能統(tǒng)計全3個指標鹏控,猜想是因為跳轉(zhuǎn)影響了友盟統(tǒng)計。
這個戳單頁面數(shù)據(jù)采集
如果需要準確的數(shù)據(jù)肤寝,可能需要重寫 history.replaceState 在方法当辐,在里面加上自定義的統(tǒng)計。
參考
https://segmentfault.com/q/1010000011840948/a-1020000011947156
https://www.cnblogs.com/caizhenbo/p/6679478.html
https://blog.csdn.net/TMQ1225/article/details/80454066
https://www.kancloud.cn/kancloud/javascript-standards-reference/46507#performancegetEntries_137