在我们的日常前端开发中,我们很少去关注我们开发应用的各个环节的一个性能,消耗时间等问题,也几乎没去统计过。但是作为一个专业的前端开发人员,这个前端的性能监控我们还是有必要知道的,下面我们就一起来看看如何进行前端的性能监控。

首先,如果我们要监控前端的一个性能,我们需要从以下几个点来进行监控:
1、监控静态资源的加载情况
2、监听ajax的发送情况
3、页面的错误捕获
4、监控用户的行为

我们围绕以上几个点来进行练习

一:监控静态资源的加载情况(算时间差)

首先,我们来看一张图:

前端监控从头 前端监控怎么做_javascript


这种图就反应了我们一个界面从浏览器打开,渲染的各个不同时间段会执行的一个函数方法

这些方法都是基于浏览器的一个API,Performance,单位是毫秒

前端监控从头 前端监控怎么做_前端监控从头_02


下面我们就来写一个方法来记录一个界面从输入访问地址到界面渲染完成的不同时间段所花费的时间吧:

let processData = (p)=> {
            let data = {
                prevPage: p.fetchStart - p.navigationStart, // 上一个页面到这个界面的时间
                redirect: p.redirectEnd - p.redirectStart, // 重定向的时长
                dns: p.domainLookupEnd - p.domainLookupStart, //dns的解析时长
                connect: p.connectEnd - p.connectStart, // tcp的连接时长

                // 从请求到相应的时长
                send: p.responseEnd - p.requestStart, // 响应结束到请求结束的时长
                ttfb: p.responseStart - p.navigationStart, // 首字节接收到的时长
                domready: p.domInteractive - p.domLoading, // dom准备的时长
                
                // 白屏
                whiteScreen: p.domLoading - p.navigationStart,
                //dom 解析时间
                dom: p.domComplete - p.domLoading,
                // onload 的执行时间
                load: p.loadEventEnd - p.loadEventStart,
                //总时长
                total: p.loadEventEnd - p.navigationStart
            }
            return data
        }

当我们界面加载完成而且performance.timing.loadEventEnd有值的时候,我们就可以就行统计了,loadEventEnd表示dom加载完成了

let load = cb => {
            let timer;
            let check = ()=> {
                if(performance.timing.loadEventEnd){
                    // 如果已经统计完成了
                    clearTimeout(timer)
                    cb()
                }else {
                    setTimeout(check,100);
                }
            }
            window.addEventListener('load',check,false)
        }
        
        function init(cb){
            load(()=>{
                let perData = performance.timing;
                let data = processData(perData);
                cb(data)
            })
        }
        init((data)=>{
            console.log(data)
        })
// 得到的结果就是页面上各个执行时间段的时间
        // {
        //     prevPage: 0
        //     redirect: 0
        //     dns: 0
        //     connect: 0
        //     send: 2
        //     ttfb: 0
        //     domready: 34
        //     whiteScreen: 18
        //     dom: 35
        //     load: 0
        //     total: 53
        // }

我们也可以使用PerformanceObserver这个api来监听静态资源(js,html,css)的加载情况

let processData2 = (_)=> {
            let data = {
                name: _.name,
                initiatorType: _.initiatorType,
                duration: _.duration
            }
            return data
        }
        function init(cb){
            if(window.PerformanceObserver){
                let observer = new PerformanceObserver(list => {
                    let data = list.getEntries();// data是一个数组类型的
                    cb(processData2[data[0]])
                })
                observer.observe({entryTypes: ['resource']});
            }else {
                window.onload = function (){
                    let resourceData = performance.getEntriesByType('resource');
                    let data = resourceData.map(_=>processData2());
                    cb(data)
                }
            }
        }

list.getEntries() => 数据

前端监控从头 前端监控怎么做_js_03

二: 监听ajax的的发送情况
原理就是重写ajax的send方法:

let xhr = window.XMLHttpRequest;
    let oldSend = xhr.prototype.send;// 保存老的send方法
    // 重写新的send方法
    xhr.prototype.send = function(value){
        let start = Date.now();
        let fn = (type)=> {
            this.info.time = Date.now() - start;
            this.info.requestSize = value.length;
            this.info.responseSize = this.responseText.length;
            this.info.type = type;
            cb(this.info);
        }
        this.addEventListener('load',fn('load'),false);
        this.addEventListener('error',fn('error'),false);
        this.addEventListener('abort',fn('abort'),false);
        return oldSend.apply(this,arguments)
    }

这样子每次发送完请求之后就能得到这个请求过程中所用的时间

三:页面的错误捕获

function errorCapture(cb){
        window.onerror = function(message,source,lineno,colno,error){
            console.log('error:',error);
            let info = {
                message: error.message,
                name: error.name
            }
            //error.stack 属性是一个字符串,描述代码中 Error 被实例化的位置。
            let stack = error.stack;
            console.log('stack',stack);
            // 匹配报错之后的文件路径,我在本地所有是file协议,一般上线之后就是http或者https
            let matchUrl = stack.match(/file:\/\/[^\n]*/)[0];
            console.log('matchUrl:',matchUrl);
            // 匹配报错的js文件,这里由于我是用的一个html文件写的js,所以就用来匹配html文件的报错
            info.infoName = matchUrl.match(/file:\/\/(?:\S*)\.html/)[0];
            this.console.log(info.infoName);
            // 匹配报错的行和列
            let [,row,column] = matchUrl.match(/:(\d+):(\d+)/);
            info.row = row;
            info.column = column; //上线的时候代码会被压缩,source-map找到对应的真实的报错
            cb(info)
        }

    }
    errorCapture((data)=> {
        console.log('error:',data)
    })
    // 获取到的报错结果
        {
        message: "xxasd is not defined",
        name: "ReferenceError",
        infoName: ,"file:///C:/Users/Administrator/Desktop/%E5%8D%9A%E5%AE%A2/%E7%9B%91%E6%8E%A7/index.html",
        row: "155",
        column: "17",
    }

    **// 这儿会报错**
    console.log(xxasd)

**这样子就可以监控到界面中的报错信息了
注意:该捕获错误的方法不能捕获到图片404的错误,需要使用window.addEventListener(‘error’,…)事件。

而且没办法捕获promise的报错,如果你想捕获promise的报错,那么有两种方法:
1、改写 Promise,使用一个非系统原生的 Promise 库,并改写内部异常捕获的逻辑,将异常抛出。(适合框架开发者)
2、在 Promise 中异步抛出异常,比如 setTimeout(function(){ throw new Error() }) 来抛出想被全局捕获的异常。(适合业务开发者)**

四:监控用户的行为
使用事件委托的方式去获取点击的dom元素