性能监控和错误上报只是辅助功能,不应阻塞页面加载,因此只有当页面完成加载后,我们才进行数据获取和上报(实际上,页面加载完成前也获取不到必要信息),性能数据可以在页面加载完之后上报,尽量不要对页面性能造成影响。

上报时机

使用window.requestIdleCallback(),该方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。

window.onload = () => { 
    if (window.requestIdleCallback) {
        window.requestIdleCallback(() => {
            monitor.performance = getPerformance()
            monitor.resources = getResources()
        })
    } else {
        setTimeout(() => {
            monitor.performance = getPerformance()
            monitor.resources = getResources()
        }, 0)
    }
}

当然,你也可以设一个定时器,循环上报。不过每次上报最好做一下对比去重再上报,避免同样的数据重复上报。

数据上报

对于一个埋点方案来说,数据上报有两个点需要着重考虑:

  • 对跨域做特殊处理。
  • 页面销毁后,如何还能够将未上传的埋点数据成功上报?

传统的XHR发送数据请求的方式,对上面提到的两个点都无能为力。在数据上报过程中,较为常用的有两种方式:

  • 动态创建img标签,在 img.src 中拼接url的方式发送请求
  • 调用 sendBeacon 接口发送数据

new img

利用img标签的src属性发送请求的方式,具体执行方案如下:

let _img = new Image();
_img.src = `${url}?${util.spliceParam(params)}`;
_img.onload = _img.onerror = function() {}

它非常契合埋点数据上报这个应用场景:

  1. 只上报的数据不需要接收响应;
  2. img的src属性天然地不存在跨域问题。

这种使用方式也存在缺陷。首先对于src 中的URL内容是有大小限制的,太大的数据量不适用。详细看这里。其次,在页面卸载的时候,若存在数据未发送的情况,会先将对应的数据发送完,再执行页面卸载。这种情况下,会在体验上给使用者带来不方便。

sendBeacon

sendBeacon方法是一个异步、非阻塞的数据传输方法。具体使用方式如下:

window.navigator.sendBeacon && window.navigator.sendBeacon(url, params)

它的特点是:

  • Beacon请求是Post方式。
  • Beacon请求优先避免与关键操作和更高优先级的网络请求竞争。
  • Beacon请求可以有效地合并,以优化移动设备上的能量使用。
  • Beacon保证页面卸载之前启动信标请求,并允许运行完成且不会阻塞请求或阻塞处理用户交互事件的任务。
  • 返回值:sendBeacon 方法被执行后返回一个布尔值,true代表用户代理成功地将信标请求加入到队列中,否则返回false

对于sendBeacon方法,它的局限性体现在:

  • 不能跨域,需要服务端设置。
  • 新特性接口,兼容性存在问题。

因此,在实际的应用过程中,需要针对实际情况,结合 Img 标签 与 sendBeacon 的方式一起使用。