1.首次给系统建立链接给客户端分配服务id,主要用于建立客户端标识,减少没必要的重复数据。
2. 根据Performance API获取前端性能数据,获取首次页面加载的性能数据
let times = {};
let t = window.performance.timing;
//重定向时间
times.redirectTime = t.redirectEnd - t.redirectStart;
//dns查询耗时
times.dnsTime = t.domainLookupEnd - t.domainLookupStart;
//TTFB 读取页面第一个字节的时间
times.ttfbTime = t.responseStart - t.navigationStart;
//DNS 缓存时间
times.appcacheTime = t.domainLookupStart - t.fetchStart;
//卸载页面的时间
times.unloadTime = t.unloadEventEnd - t.unloadEventStart;
//tcp连接耗时
times.tcpTime = t.connectEnd - t.connectStart;
//request请求耗时
times.reqTime = t.responseEnd - t.responseStart;
//解析dom树耗时
times.analysisTime = t.domComplete - t.domInteractive;
//白屏时间
times.blankTime = t.domLoading - t.fetchStart;
//domReadyTime
times.domReadyTime = t.domContentLoadedEventEnd - t.fetchStart;
2.异步获取需要分析的数据
let entryTimesList = [];
let entryList = window.performance.getEntries();
entryList.forEach((item,index)=>{
let templeObj = {};
let usefulType = ['script','css','fetch','xmlhttprequest','link','img'];
if(usefulType.indexOf(item.initiatorType)>-1){
templeObj.name = item.name;
templeObj.nextHopProtocol = item.nextHopProtocol;
//dns查询耗时
templeObj.dnsTime = item.domainLookupEnd - item.domainLookupStart;
//tcp链接耗时
templeObj.tcpTime = item.connectEnd - item.connectStart;
//请求时间
templeObj.reqTime = item.responseEnd - item.responseStart - startTime;
//重定向时间
templeObj.redirectTime = item.redirectEnd - item.redirectStart;
entryTimesList.push(templeObj);
}
});
3.错误监听
3-1:window js语法报错
window.onerror = function (e) {
//...deal with something
}
3-2:promise catch错误捕获未处理
window.addEventListener('unhandledrejection', (e) => {
console.log(e, 'promise事件回调监控')
})
3-3:vue的错误捕获
//vue错误捕获
let vuePlugin = function (error, _Vue) {
if (!_Vue || !_Vue.config) {
return;
}
let _oldOnError = _Vue.config.errorHandler;
_Vue.config.errorHandler = function (errMsg, vm, info) {
error(errMsg)
if (typeof console != 'undefined' && typeof console.error == 'function') {
console.error(errMsg)
}
if (typeof _oldOnError == 'function') {
_oldOnError.call(this, errMsg, vm, info)
}
}
}
try {
vuePlugin(window.onerror, Vue)
} catch (error) {
}
3-4获取跨域的JavaScript错误,对监听器进行拦截重写,默认情况下a域名下的js是抓不到b域名下js的报错信息
//跨域javasctipt错误
const originAddEventListener = EventTarget.prototype.addEventListener,errObj={};
EventTarget.prototype.addEventListener = (type, listener, options) => {
const wrappedListener = (...args) => {
errObj.dedetail = {//获取最后事件的dom节点信息
outerHTML:args[0].srcElement.outerHTML,
"className":args[0].srcElement.className,
"id":args[0].srcElement.id,
"tagName":args[0].srcElement.nodeName
}
errObj.type = type
try {
if(args[0].srcElement.nodeName!=undefined){
return listener.apply(this, args)
}
} catch (error) {
throw error
}
}
return originAddEventListener.call(this, type, wrappedListener, options)
}
4.何时上传数据
- 页面加载和重新刷新
- 页面切换路由
- 页面所在的tab标签重新变得可见
- 页面报错
- 网络请求
对上述的前3种场景,特别是切换路由的情况,如果切换路由是通过改变hash值来实现的,那么只需要监听hashchange事件,如果是通过html5的history api来改变url的,那么需要重新定义pushstate和replacestate事件。
直接给出history实现路由场景下监听url改变的方案:
var _wr = function(type) {
var orig = history[type];
return function() {
var rv = orig.apply(this, arguments);
var e = new Event(type);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _wr('pushState');
history.replaceState = _wr('replaceState');
然后我们就可以根据上述场景,分别监听相应的事件,从而实现前端性能数据的上报:
addEvent(window,'load',function(e){
...deal with something
});
//监控history基础上实现的单页路由中url的变化
addEvent(window,'replaceState', function(e) {
...deal with something
});
addEvent(window,'pushState', function(e) {
...deal with something
});
//通过hash切换来实现路由的场景
addEvent(window,'hashchange',function(e){
...deal with something
});
addEvent('document','visibilitychang',function(e){
...deal with something
})
5.使用sendBeacon方法上传数据
该方法发送数据没有返回值,不需要握手协议(在页面销毁期,可以异步的发送数据,因此不会造成类似同步ajax请求那样的阻塞问题,也不会影响下一个页面的渲染),下边对该方法做了简单的封装。
p:navigator.sendBeacon的兼容性如下,并不是很好,因此我们可以判断请求navigator.sendBeacon不支持时用XMLHttpRequest去请求。
const reportData = (url, data) => {
const formData = new FormData();
Object.keys(data).forEach((key) => {
let value = data[key];
if (typeof value !== 'string') {
// formData只能append string 或 Blob
value = JSON.stringify(value);
}
formData.append(key, value);
});
navigator.sendBeacon(url, formData);
};