关闭浏览器时传送资源深入探究--前端监控细节深究(3)

背景

书接上文,浏览器卸载的时候,我们会把我们当前来不及发送到后端的数据发送到后端,面对这种情况,我们有四种解决方案:

  1. 发起一个同步 XMLHttpRequest 来发送数据。
  2. 创建一个 <img> 元素并设置 src,大部分用户代理会延迟卸载(unload)文档以加载图像。
  3. 创建一个几秒的 no-op 循环。
  4. 使用 Navigator.sendBeacon() API。

方案一

  • 发起一个同步 XMLHttpRequest 来发送数据。
js 复制代码
const syncReport = (url, { data = {}, headers = {} } = {}) => {
  const xhr = new XMLHttpRequest();
  xhr.open('POST', url, false);
  xhr.withCredentials = true;
  Object.keys(headers).forEach((key) => {
    xhr.setRequestHeader(key, headers[key]);
  });
  xhr.send(JSON.stringify(data));
};

xhr请求改为同步,虽然能够完成发送数据,但存在以下两个问题:

  1. 部分浏览器已经不支持同步的 XMLHttpRequest 对象了(即open()方法的第三个参数为false);
  2. xhr请求改为同步后,会迫使用户代理延迟卸载文档,并使得下一个导航出现的更晚。下一个页面对于这种较差的载入表现无能为力。

方案二

  • 通过在unload事件处理器中,创建一个图片元素并设置它的 src 属性的方法来延迟卸载以保证数据的发送。因为绝大多数浏览器会延迟卸载以保证图片的载入,所以数据可以在卸载事件中发送。
js 复制代码
const reportData = (url, data) => {
  let img = document.createElement('img');
  const params = [];
  Object.keys(data).forEach((key) => {
    params.push(`${key}=${encodeURIComponent(data[key])}`);
  });
  img.onload = () => img = null;
  img.src = `${url}?${params.join('&')}`;
};

存在的问题:

  • 会迫使用户代理延迟卸载文档,并使得下一个导航出现的更晚。下一个页面对于这种较差的载入表现无能为力。

方案三

  • unload 事件里面,加一些很耗时的同步操作。这样就能留出足够的时间,保证异步 AJAX 能够发送成功。
js 复制代码
function log() {
  let xhr = new XMLHttpRequest();
  xhr.open('post', '/log', true);
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.send('foo=bar');
}
​
window.addEventListener('unload', function(event) {
  log();
​
  // a time-consuming operation
  for (let i = 1; i < 10000; i++) {
    for (let m = 1; m < 10000; m++) { continue; }
  }
});

方案四

使用 Navigator.sendBeacon()

navigator.sendBeacon() 方法可用于通过 HTTP POST 将少量数据 异步 传输到 Web 服务器。

它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术(如:XMLHttpRequest)发送分析数据的一些问题。

Navigator.sendBeacon方法接受两个参数,第一个参数是目标服务器的 URL,第二个参数是所要发送的数据(可选),可以是任意类型(字符串、表单对象、二进制对象等等)。

js 复制代码
navigator.sendBeacon(url, data)
  • data 是将要发送的数据,可以是 ArrayBuffer、ArrayBufferView、Blob、FormData、URLSearchParams 或字符串。
  • URL 是 data 将要被发送到的网络地址。
  • 这个方法的返回值是一个布尔值,成功发送数据为true,否则为false

该方法发送数据的 HTTP 方法是 POST,可以跨域,类似于表单提交数据。它不能指定回调函数。

在这里说一下 data 的三种类型:

  • DOMString类型,该请求会自动设置请求头的 Content-Type 为 text/plain
  • 如果用 Blob 发送数据,这时需要我们手动设置 Blob 的 MIME type,一般设置为 application/x-www-form-urlencoded。
js 复制代码
const reportData = (url, data) => { 
    const blob = new Blob([JSON.stringify(data), { 
        type: 'application/x-www-form-urlencoded', 
    }]); 
    navigator.sendBeacon(url, blob); 
 };
  • 发送的是Formdata类型,此时该请求会自动设置请求头的 Content-Type 为 multipart/form-data。
js 复制代码
var data = {
   name: 'ryker'  ,
   age: 20
};
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);
};

网站通常希望在用户完成页面浏览后向服务器发送分析或诊断数据,最可靠的方法是在 visibilitychange 事件发生时发送数据:

js 复制代码
document.addEventListener("visibilitychange", function logData() {
  if (document.visibilityState === "hidden") {
    navigator.sendBeacon("/log", analyticsData);
  }
});

避免使用 unload 和 beforeunload

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

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

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

Firefox 也会在 bfcache 中排除包含 beforeunload 事件处理器的页面。

使用 sendBeacon() 方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能,这意味着:

  • 数据发送是可靠的。
  • 数据异步传输。
  • 不影响下一导航的载入。

参考文章

zhuanlan.zhihu.com/p/381796039

developer.mozilla.org/zh-CN/docs/...

juejin.cn/post/684490...

zhuanlan.zhihu.com/p/435549202

mrluo.life/article/det...

相关推荐
喵叔哟12 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django