JavaScript前端埋點(diǎn)神器navigator.sendBeacon詳細(xì)指南
前言
在前端開發(fā)中,埋點(diǎn)系統(tǒng)是必不可少的一環(huán)。我們經(jīng)常需要在用戶關(guān)閉頁(yè)面、刷新或跳轉(zhuǎn)路由時(shí),向服務(wù)器發(fā)送最后一條統(tǒng)計(jì)數(shù)據(jù)(比如用戶停留時(shí)長(zhǎng)、頁(yè)面跳出率)。
但這看似簡(jiǎn)單的需求,在實(shí)現(xiàn)時(shí)卻危機(jī)四伏:請(qǐng)求發(fā)不出去?頁(yè)面跳轉(zhuǎn)卡頓?今天我們就來(lái)聊聊這個(gè)問(wèn)題的終極解決方案 —— navigator.sendBeacon。
一、 痛點(diǎn)與傳統(tǒng)方案的掙扎
場(chǎng)景還原
當(dāng)用戶點(diǎn)擊關(guān)閉按鈕時(shí),瀏覽器會(huì)觸發(fā)生命周期事件(unload 或 visibilitychange)。如果我們直接使用普通的異步 AJAX (xhr 或 fetch) 發(fā)送請(qǐng)求,瀏覽器通常會(huì)忽略它,因?yàn)轫?yè)面都要銷毀了,瀏覽器不想處理未完成的請(qǐng)求。
傳統(tǒng)方案:同步 XHR
為了保證數(shù)據(jù)能發(fā)出去,以前的做法是將請(qǐng)求改為同步(Synchronous) 。
const syncReport = (url, { data = {}, headers = {} } = {}) => {
const xhr = new XMLHttpRequest();
// 第三個(gè)參數(shù) false 表示同步請(qǐng)求
xhr.open('POST', url, false);
xhr.withCredentials = true;
Object.keys(headers).forEach((key) => {
xhr.setRequestHeader(key, headers[key]);
});
xhr.send(JSON.stringify(data));
};
致命缺陷
- 用戶體驗(yàn)極差:同步請(qǐng)求會(huì)阻塞主線程。這意味著只有請(qǐng)求發(fā)送完成,頁(yè)面才能關(guān)閉或跳轉(zhuǎn)。在弱網(wǎng)環(huán)境下,用戶會(huì)感覺頁(yè)面“卡死”了。
- 瀏覽器廢棄:現(xiàn)代瀏覽器(如 Chrome)已經(jīng)明確表示將在頁(yè)面卸載期間禁用同步 XHR,這種方法遲早失效。
二、 救世主:navigator.sendBeacon
1. 概念
navigator.sendBeacon() 是專門為“頁(yè)面卸載時(shí)發(fā)送數(shù)據(jù)”而設(shè)計(jì)的 Web API。 它的核心能力是:將數(shù)據(jù)放入瀏覽器的發(fā)送隊(duì)列,即使頁(yè)面已經(jīng)關(guān)閉,瀏覽器也會(huì)在后臺(tái)默默完成發(fā)送。
2. 核心優(yōu)勢(shì)
- 可靠性高:不受頁(yè)面生命周期影響,確保數(shù)據(jù)不丟失。
- 非阻塞:完全異步執(zhí)行,不會(huì)阻塞頁(yè)面關(guān)閉或跳轉(zhuǎn),用戶體驗(yàn)絲滑。
- 低優(yōu)先級(jí):瀏覽器會(huì)擇機(jī)發(fā)送(通常是網(wǎng)絡(luò)空閑時(shí)),不爭(zhēng)搶關(guān)鍵資源。
3. API 語(yǔ)法
const result = navigator.sendBeacon(url, data);
url:請(qǐng)求地址。data:要發(fā)送的數(shù)據(jù),支持ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData或URLSearchParams。result(返回值):布爾值 (true/false)。true:表示數(shù)據(jù)成功加入傳輸隊(duì)列(注意:不代表服務(wù)器接收成功)。false:表示隊(duì)列已滿,無(wú)法加入。
三、 實(shí)戰(zhàn):三種常見發(fā)送姿勢(shì)
1. 發(fā)送普通字符串
默認(rèn) Content-Type 為 text/plain。
const reportData = (url, data) => {
// data 可能會(huì)被轉(zhuǎn)為字符串 "[object Object]",建議先 stringify
navigator.sendBeacon(url, JSON.stringify(data));
};
2. 發(fā)送 JSON 數(shù)據(jù)(推薦)
如果你希望后端接收到的 Content-Type 是 application/json 或者 application/x-www-form-urlencoded,需要使用 Blob 來(lái)手動(dòng)指定。
const reportData = (url, data) => {
// ? 正確寫法:Blob 的第二個(gè)參數(shù)才是 options
const blob = new Blob([JSON.stringify(data)], {
type: 'application/json; charset=UTF-8' // 或者 application/x-www-form-urlencoded
});
navigator.sendBeacon(url, blob);
};
3. 發(fā)送 FormData
適用于需要上傳文件或模擬表單提交的場(chǎng)景。瀏覽器會(huì)自動(dòng)設(shè)置 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);
};
四、跨域場(chǎng)景的“萬(wàn)能鑰匙” —— 1px 像素圖片
在某些場(chǎng)景下使用sendBeacon 會(huì)有跨域問(wèn)題,而使用1px像素圖片這種方式則利用了瀏覽器允許跨域加載資源(如圖片、腳本)的特性,繞過(guò)了復(fù)雜的 CORS 配置
1. 核心原理
通過(guò)動(dòng)態(tài)創(chuàng)建 Image 對(duì)象,將埋點(diǎn)數(shù)據(jù)通過(guò) URL Query 的形式掛載在圖片請(qǐng)求的地址后面。服務(wù)端在接收到請(qǐng)求后,記錄日志并返回一個(gè) 1x1 像素的透明圖片。
2. 代碼實(shí)現(xiàn)
/**
* 跨域埋點(diǎn)發(fā)送:1px 像素圖片方案
* @param {string} url - 接口地址
* @param {Object} data - 埋點(diǎn)數(shù)據(jù)
*/
const reportByImg = (url, data) => {
// 1. 構(gòu)造查詢參數(shù)字符串
const params = Object.keys(data)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
.join('&');
// 2. 創(chuàng)建圖片實(shí)例
const img = new Image();
// 3. 監(jiān)聽回調(diào)(可選,用于監(jiān)控發(fā)送是否成功)
img.onload = () => console.log('埋點(diǎn)發(fā)送成功');
img.onerror = (err) => console.error('埋點(diǎn)發(fā)送失敗', err);
// 4. 添加時(shí)間戳用于確保每次請(qǐng)求都被視為獨(dú)立的新資源,避免被一些代理服務(wù)器或?yàn)g覽器機(jī)制意外緩存
const connector = url.includes('?') ? '&' : '?';
img.src = `${url}${connector}${params}&_t=${Date.now()}`;
};
3. 方案對(duì)比:為什么不用其他方式?
| 特性 | 1px Image | Ajax (XHR/Fetch) | sendBeacon |
|---|---|---|---|
| 跨域限制 | 無(wú)(天然支持) | 嚴(yán)格(需服務(wù)端配置 CORS) | 較松(但仍受限于安全策略) |
| 頁(yè)面卸載 | 可能被截?cái)?/td> | 極易被截?cái)?/td> | 可靠(異步非阻塞) |
| 兼容性 | 極好(所有瀏覽器) | 一般 | 較好(現(xiàn)代瀏覽器) |
| 承載數(shù)據(jù)量 | ?。ㄊ?URL 長(zhǎng)度限制) | 大 | 中(通常 < 64KB) |
4.注意事項(xiàng)
- URL 長(zhǎng)度限制:由于數(shù)據(jù)是帶在 URL 上的,瀏覽器對(duì) URL 長(zhǎng)度有限制(通常為 2KB-8KB)。如果數(shù)據(jù)量巨大,請(qǐng)拆分發(fā)送或改用
sendBeacon。 - GIF 是首選:服務(wù)端推薦返回 GIF 格式。對(duì)比 PNG 和 JPG,GIF 的透明像素塊在文件頭開銷上是最小的(僅 43 字節(jié))。
- 內(nèi)存釋放:在一些極端高頻埋點(diǎn)場(chǎng)景下,建議在
onload之后執(zhí)行img = null徹底釋放內(nèi)存。
五、 避坑指南(面試考點(diǎn))
- 請(qǐng)求類型固定:
sendBeacon只能發(fā)送 POST 請(qǐng)求。 - 無(wú)法讀取響應(yīng):這是一個(gè)“射后不理”的 API,你無(wú)法獲取服務(wù)器返回的數(shù)據(jù)(狀態(tài)碼、Response Body 等)。
- 數(shù)據(jù)大小限制:雖然標(biāo)準(zhǔn)沒有明確規(guī)定,但瀏覽器對(duì)隊(duì)列總大小有限制(通常在 64KB 左右),不適合發(fā)送大數(shù)據(jù)。
- Cookie 攜帶:
sendBeacon默認(rèn)會(huì)攜帶同域的 Cookie。
六、 面試模擬題
Q1:sendBeacon和ajax(XHR/Fetch) 有什么根本區(qū)別?
參考回答:
- 生命周期:Ajax 請(qǐng)求屬于頁(yè)面上下文,頁(yè)面關(guān)閉時(shí)請(qǐng)求會(huì)被取消(除非同步);
sendBeacon屬于瀏覽器上下文,頁(yè)面關(guān)閉后依然存活。 - 交互體驗(yàn):頁(yè)面卸載時(shí),同步 Ajax 會(huì)阻塞跳轉(zhuǎn);
sendBeacon是異步非阻塞的。 - 功能限制:
sendBeacon只能 POST,無(wú)法自定義 headers(除了 Content-Type),且無(wú)法讀取響應(yīng)。
Q2:如果瀏覽器不支持sendBeacon怎么辦?
參考回答: 需要做降級(jí)處理。
- 檢測(cè)
navigator.sendBeacon是否存在。 - 如果不存在,降級(jí)為 同步 XHR 請(qǐng)求(雖然體驗(yàn)差,但得保數(shù)據(jù))。
- 或者使用
<img>標(biāo)簽發(fā)送 GET 請(qǐng)求(僅限數(shù)據(jù)量極小且不需要響應(yīng)的場(chǎng)景)。
Q3:sendBeacon返回true代表數(shù)據(jù)一定發(fā)送成功了嗎?
參考回答: 不一定。返回 true 僅代表瀏覽器成功將數(shù)據(jù)加入了發(fā)送隊(duì)列。如果網(wǎng)絡(luò)斷開、或者瀏覽器崩潰,數(shù)據(jù)依然可能發(fā)送失敗。但相比于普通 Ajax,它的成功率已經(jīng)高出了幾個(gè)數(shù)量級(jí)。
到此這篇關(guān)于JavaScript前端埋點(diǎn)神器navigator.sendBeacon詳細(xì)指南的文章就介紹到這了,更多相關(guān)JS前端埋點(diǎn)navigator.sendBeacon內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
純JS前端實(shí)現(xiàn)PDF從生成到下載的全過(guò)程
這段文章詳細(xì)描述了使用jsPDF生成PDF文件時(shí)遇到的三個(gè)關(guān)鍵問(wèn)題:字體兼容、文字換行和圖片渲染問(wèn)題,通過(guò)分享具體的解決方法,為遇到相同問(wèn)題的開發(fā)者提供了解決方案,需要的朋友可以參考下2026-06-06
前端JS調(diào)用后端API的3種實(shí)用方法(輕松搞定前后端數(shù)據(jù)交互)
在JavaScript開發(fā)中,調(diào)用接口是前端與后端交互的基礎(chǔ)操作之一,通過(guò)調(diào)用接口,前端頁(yè)面可以動(dòng)態(tài)獲取數(shù)據(jù),實(shí)現(xiàn)頁(yè)面的動(dòng)態(tài)展示與用戶交互,這篇文章主要介紹了前端JS調(diào)用后端API的3種實(shí)用方法,需要的朋友可以參考下2025-11-11
關(guān)于flash遮蓋div浮動(dòng)層的解決方法
關(guān)于flash遮蓋div浮動(dòng)層2010-07-07
JavaScript相等運(yùn)算符的九條規(guī)則示例詳解
這篇文章主要給大家介紹了關(guān)于JavaScript相等運(yùn)算符的九條規(guī)則,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用JavaScript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
微信小程序出現(xiàn)wx.navigateTo頁(yè)面不跳轉(zhuǎn)問(wèn)題的解決方法
這篇文章主要介紹了微信小程序出現(xiàn)wx.navigateTo頁(yè)面不跳轉(zhuǎn)問(wèn)題的解決方法,簡(jiǎn)單分析了微信小程序出現(xiàn)wx.navigateTo頁(yè)面不跳轉(zhuǎn)情況的原因及相應(yīng)的解決方法,需要的朋友可以參考下2017-12-12
JavaScript數(shù)據(jù)類型和變量_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)類型和變量的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
JavaScript高級(jí)程序設(shè)計(jì) 閱讀筆記(四) ECMAScript中的類型轉(zhuǎn)換
ECMAScript中的類型轉(zhuǎn)換,學(xué)習(xí)js的朋友可以看看2012-02-02
關(guān)于angular 8.1使用過(guò)程中的一些記錄
這篇文章主要給大家介紹了關(guān)于angular 8.1使用過(guò)程中的一些記錄,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11

