关于前端性能-监控平台的数据上报时机

625 阅读5分钟

选择一个时机上报数据对于一个监控系统来说尤为重要。有时过早的发送数据可能导致错过收集数据的机会,或者会影响页面的性能,大部分平台都是采用在页面卸载时发送数据,以下是页面卸载时的几个钩子

一、一些浏览器的页面卸载事件

beforeunload ​​event

当浏览器窗口关闭或者刷新时,会触发 beforeunload 事件。会在onunload 之前执行。

一般用来触发是否离开此页面的确认框

unload ​​event

当文档或一个子资源正在被卸载时,触发 unload事件。

pagehide ​​event

当浏览器在显示与会话历史记录不同的页面的过程中隐藏当前页面时,​​pagehide​​(页面隐藏) 事件会被发送到一个​Window​ 。例如,当用户单击浏览器的“后退”按钮时,当前页面在显示上一页之前会收到一个​​pagehide​​​(页面隐藏) 事件。

二、​​visibilitychange​

当其选项卡的内容变得可见或被隐藏时,会在文档上触发 ​​visibilitychange​​ (能见度更改) 事件。当标签页关闭或者切换标签页时都会触发改事件。

document.addEventListener("visibilitychange", function() {
  if (document.visibilityState === "visible") {
    // 页面至少一部分可见。
  } 
  if (document.visibilityState === "hidden") {
    // 页面彻底不可见
  }
  if (document.visibilityState === "prerender") {
    // 页面即将或正在渲染,处于不可见状态。
  }
});

三、​​requestidleCallback​

requestIdleCallback 是 window 属性上的方法,它的作用是在浏览器一帧的剩余空闲时间内执行优先度相对较低的任务。

空闲时段

场景一:  当浏览器一帧渲染所用时间小于屏幕刷新率(对于具有60Hz 的设备,一帧间隔应该小于16ms)时间,到下一帧渲染渲染开始时出现的空闲时间

场景二:   当浏览器没有可渲染的任务,主线程一直处于空闲状态,事件队列为空。为了避免在不可预测的任务(例如用户输入的处理)中引起用户可察觉的延迟,这些空闲周期的长度应限制为最大值​​50ms​​,也就是​​timeRemaining​​​最大不超过50(也就是20fps,这也是react polyfill的原因之一),当空闲时段结束时,可以调度另一个空闲时段,如果它保持空闲,那么空闲时段将更长,后台任务可以在更长时间段内发生

基本用法

 var handle = window.requestIdleCallback(callback[, options])
 callback:
一个在事件循环空闲时即将被调用的函数的引用。函数会接收到一个名为 deadline 的参数,这个参数可以获取当前空闲时间
以及回调是否在超时时间前已经执行的状态;该对象上有两个属性:
  - timeRemaining:timeRemaining属性是一个函数,函数的返回值表示返回当前空闲时间的剩余时间
  - didTimeout:didTimeout属性是一个布尔值,如果didTimeout是true,那么表示本次callback的执行是因为超时的原因

options 可选
包括可选的配置参数。具有如下属性:
timeout:如果指定了timeout并具有一个正值,并且尚未通过超时毫秒数调用回调,那么回调会在下一次空闲时期被强制执行,
尽管这样很可能会对性能造成负面影响。

四、总结

一般来说,选择合适的上报时机需要综合考虑几个因素:确定监控数据的准确和全面性、确保浏览器能发送数据成功、不对页面的性能造成影响。   

页面卸载

页面卸载其实可以分成三种情况。

  • 页面可见时,用户关闭 Tab 页或浏览器窗口。
  • 页面可见时,用户在当前窗口前往另一个页面。
  • 页面不可见时,用户或系统关闭浏览器窗口。

这三种情况,都会触发​​visibilitychange​​事件。前两种情况,该事件在用户离开页面时触发;最后一种情况,该事件在页面从可见状态变为不可见状态时触发。

由此可见,​​visibilitychange​​事件比​​pagehide​​、​​beforeunload​​、​​unload​​事件更可靠,所有情况下都会触发(从​​visible​​变为​​hidden​​)。因此,可以只监听这个事件,运行页面卸载时需要运行的代码,不用监听后面那三个事件。

甚至可以这样说,​​unload​​事件在任何情况下都不必监听,​​beforeunload​​​事件只有一种适用场景,就是用户修改了表单,没有提交就离开当前页面。另一方面,指定了这两个事件的监听函数,浏览器就不会缓存当前页面。

避免使用unload和beforeunload

过去,许多网站使用 unload 或 beforeunload 事件以在会话结束时发送统计数据。然而这是不可靠的,在许多情况下(尤其是移动设备)浏览器不会产生 unload、beforeunload 或 pagehide 事件。下面列出了一种不触发上述事件的情况:

  1. 用户加载了网页并与其交互。
  2. 完成浏览后,用户切换到了其它应用程序,而不是关闭选项卡。
  3. 随后,用户通过手机的应用管理器关闭了浏览器应用。

此外,unload 事件与现代浏览器实现的往返缓存不兼容。在部分浏览器(如:Firefox)通过在 bfcache 中排除包含 unload 事件处理器的页面来解决不兼容问题,但这存在性能损失。其它浏览器,例如 Safari 和 Android 上的 Chrome 浏览器则采取用户在同一标签页下导航至其它页面时不触发 unload 事件的方法来解决不兼容问题。 Firefox 也会在 bfcache 中排除包含 beforeunload 事件处理器的页面。

尽量使用visibilitychange

visibilitychange可以确保在页面的最后阶段稳定触发,但有一定的兼容性问题。

使用 pagehide 作为回退

可使用 pagehide (en-US) 事件来代替部分浏览器未实现的 visibilitychange 事件。和 beforeunload 与 unload 事件类似,这一事件不会被可靠地触发(特别是在移动设备上)。

适当的使用requestIdleCallback

如当一个页面出现需要上报的数据较多时, 如出现大量的js报错、静态资源报错、接口资源请求时,可以在适当的时候使用requestIdleCallback来发送错误日志。