摘要:今天我們來(lái)聊聊前端的監(jiān)控我們?yōu)槭裁葱枰岸吮O(jiān)控為了獲取用戶行為以及跟蹤產(chǎn)品在用戶端的使用情況,并以監(jiān)控?cái)?shù)據(jù)為基礎(chǔ),指明產(chǎn)品優(yōu)化方向前端監(jiān)控分為三類性能項(xiàng)目數(shù)據(jù)監(jiān)控異常監(jiān)控性能監(jiān)控衡量前端的性能的指標(biāo)是時(shí)間那么如何監(jiān)測(cè)時(shí)間呢,瀏覽器給我們提
今天我們來(lái)聊聊前端的監(jiān)控
我們?yōu)槭裁葱枰岸吮O(jiān)控 ?
為了獲取用戶行為以及跟蹤產(chǎn)品在用戶端的使用情況,并以監(jiān)控?cái)?shù)據(jù)為基礎(chǔ),指明產(chǎn)品優(yōu)化方向
前端監(jiān)控分為三類
性能項(xiàng)目
數(shù)據(jù)監(jiān)控
異常監(jiān)控
性能監(jiān)控- 衡量前端的性能的指標(biāo)是時(shí)間
那么如何監(jiān)測(cè)時(shí)間呢, 瀏覽器給我們提供了一個(gè) API performance
來(lái)看看里面都有什么吧
它的屬性 timing 是一個(gè)PerformanceTiming 對(duì)象 , 包含了延遲相關(guān)的性能,timing里的屬性基本都是成雙成對(duì)的 都是以xxxstart --- xxxend end跟start的時(shí)間差就是我們所需要的信息
那么我們來(lái)捋一捋網(wǎng)頁(yè)打開(kāi)到關(guān)閉 都有哪些時(shí)間戳記錄下來(lái),看一張網(wǎng)頁(yè)加載流程圖
首先是navigationStart 就是地址欄開(kāi)始加載 期間會(huì)執(zhí)行unload 卸載上一個(gè)網(wǎng)頁(yè)的內(nèi)容 如果有重定向(同域名),就開(kāi)始重定向。
fetchStart 抓取開(kāi)始 頁(yè)面開(kāi)始加載
domainLookupStart dns 解析開(kāi)始
domainLookupEnd dns 解析結(jié)束
connectStart tcp 握手開(kāi)始
connectEnd 建立連接
requestStart 請(qǐng)求頁(yè)面開(kāi)始
responseStart 響應(yīng)開(kāi)始
responseEnd 響應(yīng)結(jié)束
domloading dom開(kāi)始加載
domInteractive dom結(jié)構(gòu)樹(shù)加載完成(src外鏈資源未完成)
domcontentLoaded($(function(){}))
domComplete dom 加載完畢 外鏈資源加載完畢
loadevent (window.onload=function(){} 里面的代碼加載完畢)
那么我們開(kāi)始寫個(gè)監(jiān)控吧 先起一個(gè)服務(wù)
serve.js
let Koa = require("koa") let Server = require("koa-static") let path = require("path") let app = new Koa() app.use(Server(path.resolve(__dirname))) app.listen(3000,function(){ console.log("Server is running on port 3000") })
將serve作為靜態(tài)資源目錄 服務(wù)起在端口3000
再新建performance.js
performance.js
// 性能監(jiān)控 let time = ()=>{ let timing = performance.timing let data = { prevPage: timing.fetchStart - timing.navigationStart , // 上一個(gè)頁(yè)面卸載到新頁(yè)面的時(shí)間 redirect: timing.redirectEnd - timing.redirectStart , // 重定向時(shí)長(zhǎng) dns: timing.domainLookupEnd - timing.domainLookupStart, // dns 解析時(shí)長(zhǎng) tcp: timing.connectEnd - timing.connectStart ,// tcp 鏈接時(shí)長(zhǎng) respone: timing.responseEnd - timing.requestStart, // 響應(yīng)時(shí)長(zhǎng) ttfb: timing.responseStart - timing.navigationStart, // 首字節(jié)接收到 的時(shí)長(zhǎng) domReady:timing.domInteractive - timing.domLoading, // dom 準(zhǔn)備時(shí)長(zhǎng) whiteScreen:timing.domLoading - timing.navigationStart, // 白屏?xí)r間 dom:timing.domComplete - timing.domLoading, // dom解析時(shí)間 load:timing.loadEventEnd - timing.loadEventStart, total:timing.loadEventEnd - timing.navigationStart } return data } // 因?yàn)闄z測(cè)代碼在load里執(zhí)行 所以此時(shí)load事件未完成 檢測(cè)不到load的時(shí)間 所以我們需要設(shè)置一個(gè)定時(shí)器來(lái)監(jiān)聽(tīng)load完成 let timeout = (cb)=>{ let timer; let check = ()=>{ // 加載完畢后才有performance.timing.loadEventEnd if(performance.timing.loadEventEnd){ timer = null cb() }else{ timer = setTimeout(check,100) } } window.addEventListener("load",check,false) } let domReady = (cb)=>{ let timer; let check = ()=>{ // performance.timing.domInteractive if(performance.timing.domInteractive){ timer = null cb() }else{ timer = setTimeout(check,100) } } window.addEventListener("DOMContentLoaded",check,false) } let _performance = { init(cb){ //有可能domloaded 并未加載好 用戶就關(guān)閉網(wǎng)頁(yè)了 這種情況也希望監(jiān)聽(tīng)到 domReady(()=>{ let data = time() data.type = "domReady" cb(data) }) // dom完全加載完畢 timeout(()=>{ let data = time() data.type = "loaded" cb(data) }) } } // 通過(guò)_performance.init(cb) 獲取到監(jiān)控?cái)?shù)據(jù) _performance.init((data)=>{console.log(data)})
html 引入performance.js
運(yùn)行結(jié)果為:
然后發(fā)送圖片到服務(wù)器
// 通過(guò)_performance.init(cb) 獲取到監(jiān)控?cái)?shù)據(jù) let formatter = (data)=>{ let arr = [] for(key in data){ arr.push(`${key}=${data[key]}`) } return arr.join("&") } _performance.init((data)=>{ // 然后我們創(chuàng)建一張空?qǐng)D片把數(shù)據(jù)發(fā)給服務(wù)器 let img = new Image() img.src = "/p.gif?" + formatter(data) })監(jiān)控頁(yè)面資源加載情況
想要監(jiān)控資源就得獲取到__proto__ 上的getEntriesByType("resource")方法
獲取到的是個(gè)數(shù)組 包含了資源請(qǐng)求的時(shí)間 名字type 之類的 我們所做的就是拿區(qū)我們自己需要的東西
performance.js
//代碼省略 let resource = performance.getEntriesByType("resource") let data = resource.map(_=>{ return { name:_.name, initatorType:_.initiatorType, duration:_.duration } }) cb(data)ajax請(qǐng)求監(jiān)控
ajax 請(qǐng)求監(jiān)控就簡(jiǎn)單了
performance.js
// 為了獲取到我們所需要的參數(shù) 我們改寫一下 XMLHttpRequest.prototype.open(method,url,isAsync) let ajax = { init(cb){ let xhr = window.XMLHttpRequest // 保存原來(lái)的open方法 let oldOpen = xhr.prototype.open console.log(oldOpen) xhr.prototype.open = function(method,url,isAsync = true,...args){ this.info = {method,url,isAsync,message:args} return oldOpen.apply(this,arguments) } let oldSend = xhr.prototype.send xhr.prototype.send = function(value){ let start = Date.now() let fn = (type) => () =>{ this.info.time = Date.now() - start this.info.requestSize = value ? value.length : 0 this.info.responeSize = this.responseText.length this.info.type = type cb(this.info) } this.addEventListener("load",fn("success"),false) this.addEventListener("error",fn("error"),false) this.addEventListener("abort",fn("abort"),false) return oldSend.apply(this,arguments) } } } ajax.init((data)=>{ console.log(data) })
index.html
// ajax 請(qǐng)求監(jiān)控 原生ajax var request = new XMLHttpRequest(); // 新建XMLHttpRequest對(duì)象 request.onreadystatechange = function () { // 狀態(tài)發(fā)生變化時(shí),函數(shù)被回調(diào) if (request.readyState === 4) { // 成功完成 // 判斷響應(yīng)結(jié)果: if (request.status === 200) { // 成功,通過(guò)responseText拿到響應(yīng)的文本: } else { // 失敗,根據(jù)響應(yīng)碼判斷失敗原因: } } else { // HTTP請(qǐng)求還在繼續(xù)... } } // 發(fā)送請(qǐng)求: request.open("GET", "/api/categories",true,"fet"); request.send();
結(jié)果如下
主要是通過(guò)window.onerror 事件來(lái)捕獲
let errorCatch = { init(cb){ window.addEventListener("error",function(message, source, lineno, colno, error) { this.info = {} let stack = error.stack; let matchUrl = stack.match(/http://[^ ]*/)[0] // 獲取報(bào)錯(cuò)路徑 this.info.filename = matchUrl.match(/http://(?:S*).js/) ? matchUrl.match(/http://(?:S*).js/)[0]:"html" // 獲取報(bào)錯(cuò)文件; let [,row,col] = stack.match(/:(d+):(d+)/) // 獲取報(bào)錯(cuò)行 列 this.info = { message:error.message, name:error.name, filename:this.info.filename, //有可能是html里的js報(bào)錯(cuò) row, col, // 真實(shí)上線的時(shí)候 需要source-map 去找到真正的行跟列 } cb(this.info) },true) } } errorCatch.init(data=>{console.dir(data)})
需要值得注意的是
promise 無(wú)法用此方法捕獲??! 需要另行監(jiān)聽(tīng)另外的事件
另外不同域的js文件不能用此方法捕獲詳細(xì) 具體查看https://www.jianshu.com/p/315...
主要方式是在document上監(jiān)聽(tīng)事件 通過(guò)事件委托獲取事件對(duì)象 封裝info 傳給后臺(tái)
綜上所訴 大致就是這樣 這里只是簡(jiǎn)單的介紹了下 需要深入了解的小伙伴請(qǐng)自行尋找相關(guān)資料, 比如火焰圖之類的
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/101777.html
摘要:性能統(tǒng)計(jì)有助于幫我們檢測(cè)網(wǎng)站的用戶體驗(yàn)。這樣,我們就輕輕松松的統(tǒng)計(jì)到了首屏?xí)r間。下一章,我們將繼續(xù)聊聊百度移動(dòng)版首頁(yè)那些事。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼): https://segmentfault.com/blog/frontenddriver 上一篇文章我們討論了,如何進(jìn)行前端日志打點(diǎn)統(tǒng)計(jì): https://segm...
摘要:性能統(tǒng)計(jì)有助于幫我們檢測(cè)網(wǎng)站的用戶體驗(yàn)。這樣,我們就輕輕松松的統(tǒng)計(jì)到了首屏?xí)r間。下一章,我們將繼續(xù)聊聊百度移動(dòng)版首頁(yè)那些事。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼): https://segmentfault.com/blog/frontenddriver 上一篇文章我們討論了,如何進(jìn)行前端日志打點(diǎn)統(tǒng)計(jì): https://segm...
摘要:所以今天,就和大家一起聊一聊前端的安全那些事兒。我們就聊一聊前端工程師們需要注意的那些安全知識(shí)。殊不知,這不僅僅是違反了的標(biāo)準(zhǔn)而已,也同樣會(huì)被黑客所利用。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼):https://segmentfault.com/blog... 隨著互聯(lián)網(wǎng)的發(fā)達(dá),各種WEB應(yīng)用也變得越來(lái)越復(fù)雜,滿足了用戶的各種需求...
摘要:歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面不僅僅是代碼作為現(xiàn)代應(yīng)用,的大量使用,使得前端工程師們?nèi)粘5拈_(kāi)發(fā)少不了拼裝模板,渲染模板。我們今天就來(lái)聊聊,拼裝與渲染模板的那些事兒。一改俱改,一板兩用。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼):https://segmentfault.com/blog...
閱讀 881·2021-11-25 09:43
閱讀 1780·2021-09-29 09:42
閱讀 1958·2019-08-30 15:55
閱讀 3470·2019-08-30 15:54
閱讀 2680·2019-08-30 13:20
閱讀 3565·2019-08-29 13:25
閱讀 979·2019-08-28 18:03
閱讀 1842·2019-08-26 13:44