前言
在前端开发中,埋点系统是必不可少的一环。我们经常需要在用户关闭页面、刷新或跳转路由时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。
但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 —— 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);
三、 实战:三种常见发送姿势
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. 代码实现