這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 在當下前後端分離的主流環境下,前端部分的優化變得越來越重要。為了提升前端的性能和用戶體驗,我覺得可能需要從三個維度採集數據進行分析。 前端埋點。通過埋點收集和統計網頁的UV/PV、設備型號、瀏覽器等數據進行分析,比如可以有針對性對使 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
在當下前後端分離的主流環境下,前端部分的優化變得越來越重要。為了提升前端的性能和用戶體驗,我覺得可能需要從三個維度採集數據進行分析。
- 前端埋點。通過埋點收集和統計網頁的UV/PV、設備型號、瀏覽器等數據進行分析,比如可以有針對性對使用比較靠前的設備、瀏覽器等做優化和體驗。
- 網頁性能收集和監控。 採集一個頁面從請求開始到完成這個過程中的數據指標。比如收集和監控首屏載入時間、dom渲染時長、響應比較慢的介面等。有了這些數據可以很直觀和針對性的對網頁的性能進行優化和升級。
- 錯誤收集和監控。收集網頁中的js的報錯、靜態資源載入報錯,保證網頁正常訪問和降低bug率。
1.前端埋點
採集用戶的行為,監控產品在用戶端的使用情況,根據數據可以明確,前端可以針對性的做優化和體驗。
可以簡單的初步建立這樣的數據模型:
{ ip: ip地址,統計uv ua: 瀏覽器的userAgent //方便區分瀏覽器的品牌 os: 系統名稱 current_page_all: 當前頁面訪問總次數 width:瀏覽器寬度, height:瀏覽器高度, //統計瀏覽器的尺寸 current_enter_time: 進入當前頁面的時間戳 current_leave_time: 離開當前頁面的時間戳 project_id: 項目的id, url: 當前url, user_uni_id: 臨時分配給用戶唯一的id .... 還有其他 }
2.在網頁中植入對應的js代碼
//進入 document.addEventListener('DOMContentLoaded',function(){ ... let args = { ua: navigator.userAgent, os: navigator.platform, width: document.body.clientWidth || document.documentElement.clientWidth, height: document.body.clientHeight || document.documentElement.clientHeight, project_id: md5('abcd'), user_uni_id: '臨時分配給用戶唯一的id', url: window.location.href, current_enter_time: new Date().getTime() .... }; let img = new Image(); img.onload = function() { img = null; }; img.src= `https://localhost:9700/bury.gif?args=${qs(args)}`; }); //離開 window.onbeforeunload = function() { ... let args = { ... leave_time: new Date().getTime() .... }; let img = new Image(); img.onload = function() { img = null; }; img.src= `https://localhost:9700/bury.gif?args=${qs(args)}`; }
- 收集數據並上報
const fs = require('fs'); route.get('/bury.gif',async (ctx)=>{ let queryStr = ctx.querystring; let d = new Date(); let year = d.getFullYear(); let month = d.getMonth()+1; let day = d.getDate()+1; fs.writeFile(`../logs/${year}-${month}-${day}.log`,queryStr,{flag:'a',encoding:'utf-8',mode:'0666'},function(e){}); ctx.status = 200; ctx.type = 'image/gif'; ctx.body = {}; });
簡單的設想方案是先把數據收集到文本文件里,然後定時的分析這些文本文件,然後把篩選後的放到資料庫中。
2.網頁性能收集
網頁性能主要是收集是輸入url地址到網頁請求完成資源下載完成這段時間範圍內一些請求、載入等指標。
-
比如舉幾個簡單的指標:
-
首屏載入時長
-
HTML 文檔被載入和解析完成
-
網頁載入完成時間
-
白屏時間
-
其他…
- 具體實現方案(一):
//index.html <html> <head> <script> window.startTime = Date.now(); </script> </head> <body> <script> let diff = Date.now()-window.startTime; console.log('白屏時長'+diff); document.addEventListener('DOMContentLoaded',()=>{ console.log('HTML 文檔被載入和解析完成時間'+Date.now()-window.startTime); }); window.onload = function() { console.log('網頁載入完成時間'+Date.now()-window.startTime); }; </script> <script src="a.js"></script> <script src="b.js"></script> .... </body> </html>
使用performance API
Performance是W3C性能小組引入進來的一個新的API,他可以很好的獲取到首屏載入時間、白屏時間、dns查詢時間等,是一個很方便的獲取網頁性能指標的API,而且目前大部分主流瀏覽器是支持的。
https://www.caniuse.com/
1.Performance一些常用用法的總結
let timing = window.performance.timing //白屏時間 timing.responseStart - timing.navigationStart //DNS 查詢時長 timing.domainLookupEnd - timing.domainLookupStart //request請求耗時 timing.responseEnd - timing.responseStart //HTML 文檔被載入和解析完成耗時 timing.domComplete - timing.domInteractive //網頁載入完成耗時 timing.loadEventEnd - timing.navigationStart //重定向耗時 timing.redirectEnd - timing.redirectStart; //占用的記憶體 window.performance.memory.usedJSHeapSize;
2.此外還有一些高級用法,比如可以收集一些請求和靜態資源的請求時間
let time = []; let entryLists = window.performance.getEntries(); for(let i=0;i<entryLists.length;i++) { let item = entryLists[i]; let obj = {}; let soureTypes = ['script','css','xmlhttprequest','link','img']; if(soureTypes.indexOf(item.initiatorType)>=0){ obj.name = item.name; //請求時間 obj.reqTime = item.responseEnd - item.responseStart; time.push(obj); } }
3.關於Performance的更多用法可以參考:
https://www.jianshu.com/p/1355232d525a https://blog.csdn.net/hb_zhouyj/article/details/89888646
4.收集數據上報
收集上報數據,使用koa2創建介面performance.gif
const fs = require('fs'); route.get('/performance.gif',async (ctx)=>{ let queryStr = ctx.querystring; let d = new Date(); let year = d.getFullYear(); let month = d.getMonth()+1; let day = d.getDate()+1; fs.writeFile(`../performance-logs/${year}-${month}-${day}.log`,queryStr,{flag:'a',encoding:'utf-8',mode:'0666'},function(e){}); ctx.status = 200; ctx.type = 'image/gif'; ctx.body = {}; });
3.錯誤收集和監控
錯誤收集主要就是針對js報錯、靜態資源載入等出錯信息進行收集。
- 收集js報錯最先想到的可能是try catch。
try { console.log(b); } catch(e) { console.log(e); sendErrorReq(); };
但是使用使用的話,每個頁面收集錯誤都要充斥這try catch,這樣其實是不太好的。
而且catch似乎沒辦法捕獲到非同步的操作。
try { setTimeout(()=>{ console.log(b); }) } catch(e) { console.log(e); sendErrorReq(); };
試了一下沒有執行到catch裡邊的sendErrorReq函數。
使用try catch是一種局部錯誤監聽方式。
- 用window.onerror或者是window.addEventListener(‘error’)
window.onerror
/** *msg 錯誤信息 *url 錯誤所在的頁面地址 *row 錯誤所在的行數 *col 錯誤所在的列數 **/ window.onerror = function(msg,url,row,col) { console.log(msg,url,row,col, error); }; b();
使用window.onerror可以檢測到js的報錯,但是沒法監聽靜態資源載入失敗的情況。
<img src="一個不存在的圖片地址" alt="">
window.addEventListener(‘error’)
window.addEventListener(‘error’)能夠監聽到靜態資源載入出錯
window.addEventListener('error',(e)=>{ let localName = e.srcElement.localName; let currentSrc = e.srcElement.currentSrc; if(localName=='img') { console.log(`圖片${currentSrc}載入失敗了`); sendErrorData(currentSrc); } ... // e.preventDefault(); },true);
必須設為捕獲過程中執行,否則依然無法監聽。
3. promise錯誤的收集
new Promise((resolve,reject)=>{ reject('hi'); }).catch(e=>{ console.log(e); sendErrorData(e) }); axios.get(...).catch(e=>{ console.log(e); sendErrorData(e) })
但是有種情況,如果promise不加catch的話,
沒法通過window.onerror去監聽,但是還是通過監聽unhandledrejection事件去收集的
4.unhandledrejection
window.addEventListener('unhandledrejection', event => { console.log('error:'+event.reason); sendErrorData(event.reason); }); new Promise((resolve,reject)=>{ reject('hi'); });
在看下在vue中收集報錯,以vue-cli3創建的項目進行演示
測試了一下window.onerror這種方式 無法監聽錯誤的。
在網上找了下原因
可以看到在vue的源碼里,因為如果沒有定義errorHandler就會走到logError這個方法,所以沒法使用window.onerror進行監聽。
5.errorHandler
在vue的手冊中,推薦監聽vue報錯的可以使用errorHandler這個配置方法。
//main.js Vue.config.errorHandler = function (err, vm, info) { console.log('錯誤是:', err) sendErrorData(err); } 然後隨便故意寫錯 mounted() { a(); }就能收集到報錯信息了
當然最後把收集的錯誤上報給伺服器,創建一個介面error.gif。
const fs = require('fs'); route.get('/error.gif',async (ctx)=>{ let queryStr = ctx.querystring; .... fs.writeFile(`../error-logs/${year}-${month}-${day}.log`,queryStr,{flag:'a',encoding:'utf-8',mode:'0666'},function(e){}); ctx.status = 200; ctx.type = 'image/gif'; ... });