SSF0SSF0
首页
前端
  • Node
  • Go
  • C#
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
Timeline
Github
标签
分类
首页
前端
  • Node
  • Go
  • C#
  • MySql
  • Bash
  • Git
  • Docker
  • VuePress
  • CI/CD
  • 服务器
  • 网站
  • 学习资料
  • 软件
Timeline
Github
标签
分类
  • HTML

    • html1
    • html2
  • CSS

    • Flex 布局常见问题与解决方案
  • JavaScript

    • 数据类型及引用问题
    • 处理 Blob 类型文件下载问题总结
    • localStorage 与 sessionStorage 区别
    • JavaScript 中的 script 标签问题详解
    • JavaScript 中的`this`指向问题详解
    • SessionStorage踩坑记录:它真的能"只设置一次"吗?
    • 动态加载 JS 脚本方法对比
    • 浏览器页面关闭场景下的数据上报
  • es6

    • Promise
    • es6 模块导出方式全解析
  • Vue2

    • created VS mounted 发起异步请求
    • vue2-2
  • Vue3

    • Vite + Vue3 修改 element-plus 源码
    • Vue v-if 与 v-show
    • Vue3 ref 获取组件
    • Vue3 路由传参
    • 父子组件与组件里 Hooks 加载顺序
    • 第三方组件传入参数TS提示
    • Vue3 Props 在 Hooks 中的响应性处理
    • Vue Router 的两种历史模式及部署配置详解
    • 在Vue 3项目中顺利集成Tailwind CSS的完整指南
    • Vue 3 深度选择器:deep()完全指南
  • Electron

    • 快速构建 electron + vue3 项目
  • TS

    • TS 泛型
    • 记录模板使用断言的问题
    • type 与 interface
  • WebPack

    • Webpack 介绍
  • Vite

    • Vite CLI 常见命令
    • vite 与 webpack 比较
  • 项目工程

    • 前端代码风格
    • Vue3 项目规范
    • npm 镜像问题
    • 包管理工具
    • 使用 engines 限制开发环境依赖
    • 打包与shell交互指定模式
    • 使用 pnpm 构建 Monorepo 实战指南
    • pnpm 修改依赖源码打包报错
  • 记录一下小程序
  • 控制浏览器正确保存网站账号密码的技巧

浏览器页面关闭场景下的数据上报

问题背景

在网页应用中,当用户关闭或刷新页面时,通常需要上报用户行为数据(如停留时间、浏览深度等),但这种场景存在以下挑战:

解决方案一:使用标志位防止重复请求 + 异步请求

window.addEventListener("beforeunload", () => {
  // 使用异步请求方式发送
  BuriedLogic.saveStayTime(3);
});

class BuriedLogic {
  static saveStayTime(type) {
    // ...
    httpPost(url, params, headers);
  }
}

// 使用异步请求
function httpPost(url, params = { param: {}, body: {} }, headers = {}) {
  // ...
  xhr.open("POST", fullUrl, true); // 使用异步请求
  // ...
}

优点:

  • 不会阻塞页面卸载,用户体验更好

  • 实现简单,代码逻辑清晰

缺点:

  • 异步请求在页面关闭时通常会被浏览器取消,请求显示为"cancelled"状态

  • 页面刷新会同时触发beforeunload和visibilitychange事件,导致重复发送请求(但是 2 个接口都发送失败了,所以就没考虑)

  • 异步请求在浏览器关闭时的行为不稳定,在某些浏览器或特定条件下可能会成功发送,但不能作为可靠设计依据

  • 没有有效的机制确保请求发送完成

异步请求行为解释:

异步 XMLHttpRequest 在页面关闭时的行为取决于多种因素:

  1. 请求发起的时机(如果在页面真正关闭前就已经发送,可能会部分成功)

  2. 请求的大小(小请求可能在页面关闭前完成)

  3. 浏览器的实现差异(不同浏览器处理页面关闭时的未完成请求方式不同)

  4. 网络状况(良好的网络可能增加请求成功的几率)

虽然在某些情况下异步请求可能会成功,但这是不可靠且不可预测的,不应作为稳定的解决方案。

特殊情况说明

在某些浏览器和特定条件下,可能会观察到与预期相反的现象:同步请求在页面关闭时没有发送成功,而异步请求却成功发送。这可能受多种因素影响:

  1. 浏览器的具体实现和版本差异

  2. 请求的大小和处理时长

  3. 网络环境和浏览器的资源分配策略

  4. 浏览器对同步请求的特殊限制和性能保护机制

无论如何,这种行为不具有一致性和可靠性,不应该作为设计依据。navigator.sendBeacon API 仍然是页面关闭场景下最可靠的数据发送方式。

解决方案二:使用 navigator.sendBeacon API(强烈推荐)

// 使用requestType标记请求类型
let requestType = null;

document.addEventListener("visibilitychange", () => {
  // 如果是刷新引起的visibilitychange,不重复发送请求
  if (document.hidden && requestType !== "beforeunload") {
    requestType = "visibilitychange";
    BuriedLogic.saveStayTime(2);
  }
});

window.addEventListener("beforeunload", (event) => {
  requestType = "beforeunload";

  // 准备要发送的数据
  const data = BuriedLogic.prepareStayTimeData(3);

  // 使用navigator.sendBeacon发送数据
  if (navigator.sendBeacon) {
    const url = `${API_URL_BASE}burialPoint`;

    // 构建适合sendBeacon的数据
    const blob = new Blob(
      [
        JSON.stringify({
          param: {
            key: BuriedLogic.instance.urlKey,
            type: 3,
            imgId: data.imgId,
            sourceUrl: data.sourceUrl,
            browsingDepth: data.browsingDepth,
            residenceTime: data.activeTime * 1000 || "0",
          },
        }),
      ],
      { type: "application/json" }
    );

    // 发送请求,不需要等待响应
    navigator.sendBeacon(url, blob);
  } else {
    // 降级方案:使用同步XHR请求
    BuriedLogic.saveStayTime(3);
  }

  // 重置标记
  setTimeout(() => {
    requestType = null;
  }, 100);
});

优点:

  • sendBeacon API 专为页面卸载场景设计,浏览器保证尽最大努力发送数据

  • 不会阻塞页面卸载过程,用户体验更好

  • 即使页面已关闭,浏览器也会在后台完成请求

  • 符合现代浏览器设计理念和最佳实践

  • 比同步 XHR 请求有更高的成功率,特别是在页面关闭场景

缺点:

  • 兼容性问题:部分老旧浏览器不支持此 API(需降级处理)

  • 不支持自定义请求头(除 Content-Type 外)

  • 无法获取响应内容

解决方案三:定期保存数据 + 会话恢复

为了更彻底解决数据丢失问题,可以采用定期保存数据的策略:

// 每5秒保存一次当前的数据到localStorage
setInterval(() => {
  const data = {
    lastActive: Date.now(),
    browsingDepth: BuriedLogic.instance.getBrowsingDepth(),
    activeTime: BuriedLogic.instance.activeTime,
    // 其他需要保存的数据
  };
  localStorage.setItem("tempBuriedData", JSON.stringify(data));
}, 5000);

// 在页面加载时检查是否有未发送的数据
window.addEventListener("load", () => {
  const tempData = localStorage.getItem("tempBuriedData");
  if (tempData) {
    try {
      const data = JSON.parse(tempData);
      // 如果数据时间在10分钟内,则认为是有效的未发送数据
      if (Date.now() - data.lastActive < 10 * 60 * 1000) {
        // 发送上次未完成的数据
        BuriedLogic.sendSavedData(data);
      }
    } catch (e) {
      console.error("解析保存的数据失败", e);
    }
    // 清除临时数据
    localStorage.removeItem("tempBuriedData");
  }
});

优点:

  • 即使在页面突然关闭的情况下也能恢复大部分数据

  • 可以与其他解决方案结合使用,提高数据完整性

缺点:

  • 增加了实现复杂度

  • 可能导致用户下次访问时发送的数据不够及时或精确

浏览器兼容性

navigator.sendBeacon 支持情况:

  • Chrome 39+

  • Firefox 31+

  • Safari 11.1+

  • Edge 14+

  • IE 不支持

最佳实践

  1. 首选 navigator.sendBeacon:这是浏览器专门为页面离开场景设计的 API,成功率最高

  2. 提供降级方案,在不支持 sendBeacon 的浏览器中使用同步 XHR 请求

  3. 使用标志位避免 beforeunload 和 visibilitychange 事件重复触发请求

  4. 对于特别重要的数据,考虑定期保存到 localStorage 并在下次访问时恢复上报

  5. 接受部分数据丢失的可能性:在用户体验和数据完整性之间需要平衡

  6. 服务端应设计为接受幂等请求,避免重复数据问题

  7. 如果可能,考虑在用户操作过程中就定期上报数据,而不只依赖页面关闭时机

结论

页面关闭时的数据上报永远存在一定的不确定性,无论使用哪种技术都不能 100%保证数据上报成功。navigator.sendBeacon API 是目前最佳解决方案,它专为此场景设计,提供了最高的成功率和最好的用户体验。

对于极其重要的数据,应考虑在用户使用过程中就及时上报,而不是等到页面关闭时才上报。

最后更新时间:
贡献者: 何风顺
上一页
动态加载 JS 脚本方法对比