前端开发中,埋点系统是必不可少的一环。我们经常需要在用户关闭页面、刷新或跳转路由时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。
但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 ——navigator.sendBeacon。
一、 痛点与传统方案的挣扎
场景还原
当用户点击关闭按钮时,浏览器会触发生命周期事件(unload或visibilitychange)。如果我们直接使用普通的异步 AJAX (xhr或fetch) 发送请求,浏览器通常会忽略它,因为页面都要销毁了,浏览器不想处理未完成的请求。
传统方案:同步 XHR
为了保证数据能发出去,以前的做法是将请求改为同步(Synchronous)。
1 2 3 4 5 6 7 8 9 10 |
|
致命缺陷
- 用户体验极差:同步请求会阻塞主线程。这意味着只有请求发送完成,页面才能关闭或跳转。在弱网环境下,用户会感觉页面“卡死”了。
- 浏览器废弃:现代浏览器(如 Chrome)已经明确表示将在页面卸载期间禁用同步 XHR,这种方法迟早失效。
二、 救世主:navigator.sendBeacon
1. 概念
navigator.sendBeacon()是专门为“页面卸载时发送数据”而设计的 Web API。 它的核心能力是:将数据放入浏览器的发送队列,即使页面已经关闭,浏览器也会在后台默默完成发送。
2. 核心优势
- 可靠性高:不受页面生命周期影响,确保数据不丢失。
- 非阻塞:完全异步执行,不会阻塞页面关闭或跳转,用户体验丝滑。
- 低优先级:浏览器会择机发送(通常是网络空闲时),不争抢关键资源。
3. API 语法
1 |
|
url:请求地址。data:要发送的数据,支持ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData或URLSearchParams。result(返回值):布尔值 (true/false)。true:表示数据成功加入传输队列(注意:不代表服务器接收成功)。false:表示队列已满,无法加入。
三、 实战:三种常见发送姿势
1. 发送普通字符串
默认Content-Type为text/plain。
1 2 3 4 |
|
2. 发送 JSON 数据(推荐)
如果你希望后端接收到的Content-Type是application/json或者application/x-www-form-urlencoded,需要使用Blob来手动指定。
1 2 3 4 5 6 7 |
|
3. 发送 FormData
适用于需要上传文件或模拟表单提交的场景。浏览器会自动设置Content-Type为multipart/form-data。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
四、跨域场景的“万能钥匙” —— 1px 像素图片
在某些场景下使用sendBeacon会有跨域问题,而使用1px像素图片这种方式则利用了浏览器允许跨域加载资源(如图片、脚本)的特性,绕过了复杂的 CORS 配置
1. 核心原理
通过动态创建Image对象,将埋点数据通过URL Query的形式挂载在图片请求的地址后面。服务端在接收到请求后,记录日志并返回一个 1x1 像素的透明图片。
2. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
3. 方案对比:为什么不用其他方式?
4.注意事项
- URL 长度限制:由于数据是带在 URL 上的,浏览器对 URL 长度有限制(通常为 2KB-8KB)。如果数据量巨大,请拆分发送或改用
sendBeacon。 - GIF 是首选:服务端推荐返回GIF格式。对比 PNG 和 JPG,GIF 的透明像素块在文件头开销上是最小的(仅 43 字节)。
- 内存释放:在一些极端高频埋点场景下,建议在
onload之后执行img = null彻底释放内存。
五、 避坑指南(面试考点)
- 请求类型固定:
sendBeacon只能发送POST请求。 - 无法读取响应:这是一个“射后不理”的 API,你无法获取服务器返回的数据(状态码、Response Body 等)。
- 数据大小限制:虽然标准没有明确规定,但浏览器对队列总大小有限制(通常在 64KB 左右),不适合发送大数据。
- Cookie 携带:
sendBeacon默认会携带同域的 Cookie。
六、 面试模拟题
Q1:sendBeacon和ajax(XHR/Fetch) 有什么根本区别?
参考回答:
- 生命周期:Ajax 请求属于页面上下文,页面关闭时请求会被取消(除非同步);
sendBeacon属于浏览器上下文,页面关闭后依然存活。 - 交互体验:页面卸载时,同步 Ajax 会阻塞跳转;
sendBeacon是异步非阻塞的。 - 功能限制:
sendBeacon只能 POST,无法自定义 headers(除了 Content-Type),且无法读取响应。
Q2:如果浏览器不支持sendBeacon怎么办?
参考回答:需要做降级处理。
- 检测
navigator.sendBeacon是否存在。 - 如果不存在,降级为同步 XHR请求(虽然体验差,但得保数据)。
- 或者使用
<img>标签发送 GET 请求(仅限数据量极小且不需要响应的场景)。