JavaScript前端埋点神器navigator.sendBeacon详细指南
作者:发现一只大呆瓜
前言
在前端开发中,埋点系统是必不可少的一环。我们经常需要在用户关闭页面、刷新或跳转路由时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。
但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 —— navigator.sendBeacon。
一、 痛点与传统方案的挣扎
场景还原
当用户点击关闭按钮时,浏览器会触发生命周期事件(unload 或 visibilitychange)。如果我们直接使用普通的异步 AJAX (xhr 或 fetch) 发送请求,浏览器通常会忽略它,因为页面都要销毁了,浏览器不想处理未完成的请求。
传统方案:同步 XHR
为了保证数据能发出去,以前的做法是将请求改为同步(Synchronous) 。
const syncReport = (url, { data = {}, headers = {} } = {}) => {
const xhr = new XMLHttpRequest();
// 第三个参数 false 表示同步请求
xhr.open('POST', url, false);
xhr.withCredentials = true;
Object.keys(headers).forEach((key) => {
xhr.setRequestHeader(key, headers[key]);
});
xhr.send(JSON.stringify(data));
};
致命缺陷
- 用户体验极差:同步请求会阻塞主线程。这意味着只有请求发送完成,页面才能关闭或跳转。在弱网环境下,用户会感觉页面“卡死”了。
- 浏览器废弃:现代浏览器(如 Chrome)已经明确表示将在页面卸载期间禁用同步 XHR,这种方法迟早失效。
二、 救世主:navigator.sendBeacon
1. 概念
navigator.sendBeacon() 是专门为“页面卸载时发送数据”而设计的 Web API。 它的核心能力是:将数据放入浏览器的发送队列,即使页面已经关闭,浏览器也会在后台默默完成发送。
2. 核心优势
- 可靠性高:不受页面生命周期影响,确保数据不丢失。
- 非阻塞:完全异步执行,不会阻塞页面关闭或跳转,用户体验丝滑。
- 低优先级:浏览器会择机发送(通常是网络空闲时),不争抢关键资源。
3. API 语法
const result = navigator.sendBeacon(url, data);
url:请求地址。data:要发送的数据,支持ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData或URLSearchParams。result(返回值):布尔值 (true/false)。true:表示数据成功加入传输队列(注意:不代表服务器接收成功)。false:表示队列已满,无法加入。
三、 实战:三种常见发送姿势
1. 发送普通字符串
默认 Content-Type 为 text/plain。
const reportData = (url, data) => {
// data 可能会被转为字符串 "[object Object]",建议先 stringify
navigator.sendBeacon(url, JSON.stringify(data));
};
2. 发送 JSON 数据(推荐)
如果你希望后端接收到的 Content-Type 是 application/json 或者 application/x-www-form-urlencoded,需要使用 Blob 来手动指定。
const reportData = (url, data) => {
// ✅ 正确写法:Blob 的第二个参数才是 options
const blob = new Blob([JSON.stringify(data)], {
type: 'application/json; charset=UTF-8' // 或者 application/x-www-form-urlencoded
});
navigator.sendBeacon(url, blob);
};
3. 发送 FormData
适用于需要上传文件或模拟表单提交的场景。浏览器会自动设置 Content-Type 为 multipart/form-data。
const reportData = (url, data) => {
const formData = new FormData();
Object.keys(data).forEach((key) => {
let value = data[key];
// FormData 的 value 只能是字符串或 Blob
if (typeof value !== 'string' && !(value instanceof Blob)) {
value = JSON.stringify(value);
}
formData.append(key, value);
});
navigator.sendBeacon(url, formData);
};
四、跨域场景的“万能钥匙” —— 1px 像素图片
在某些场景下使用sendBeacon 会有跨域问题,而使用1px像素图片这种方式则利用了浏览器允许跨域加载资源(如图片、脚本)的特性,绕过了复杂的 CORS 配置
1. 核心原理
通过动态创建 Image 对象,将埋点数据通过 URL Query 的形式挂载在图片请求的地址后面。服务端在接收到请求后,记录日志并返回一个 1x1 像素的透明图片。
2. 代码实现
/**
* 跨域埋点发送:1px 像素图片方案
* @param {string} url - 接口地址
* @param {Object} data - 埋点数据
*/
const reportByImg = (url, data) => {
// 1. 构造查询参数字符串
const params = Object.keys(data)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
.join('&');
// 2. 创建图片实例
const img = new Image();
// 3. 监听回调(可选,用于监控发送是否成功)
img.onload = () => console.log('埋点发送成功');
img.onerror = (err) => console.error('埋点发送失败', err);
// 4. 添加时间戳用于确保每次请求都被视为独立的新资源,避免被一些代理服务器或浏览器机制意外缓存
const connector = url.includes('?') ? '&' : '?';
img.src = `${url}${connector}${params}&_t=${Date.now()}`;
};
3. 方案对比:为什么不用其他方式?
| 特性 | 1px Image | Ajax (XHR/Fetch) | sendBeacon |
|---|---|---|---|
| 跨域限制 | 无(天然支持) | 严格(需服务端配置 CORS) | 较松(但仍受限于安全策略) |
| 页面卸载 | 可能被截断 | 极易被截断 | 可靠(异步非阻塞) |
| 兼容性 | 极好(所有浏览器) | 一般 | 较好(现代浏览器) |
| 承载数据量 | 小(受 URL 长度限制) | 大 | 中(通常 < 64KB) |
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 请求(仅限数据量极小且不需要响应的场景)。
Q3:sendBeacon返回true代表数据一定发送成功了吗?
参考回答: 不一定。返回 true 仅代表浏览器成功将数据加入了发送队列。如果网络断开、或者浏览器崩溃,数据依然可能发送失败。但相比于普通 Ajax,它的成功率已经高出了几个数量级。
到此这篇关于JavaScript前端埋点神器navigator.sendBeacon详细指南的文章就介绍到这了,更多相关JS前端埋点navigator.sendBeacon内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
