前端埋點(diǎn)數(shù)據(jù)收集及上報(bào)方案實(shí)戰(zhàn)
點(diǎn)擊上方 前端瓶子君,關(guān)注公眾號(hào)
回復(fù)算法,加入前端編程面試算法每日一題群
什么是埋點(diǎn)
埋點(diǎn),它的學(xué)名是事件追蹤(Event Tracking),主要是針對(duì)特定用戶行為或業(yè)務(wù)過(guò)程進(jìn)行捕獲、處理和發(fā)送的相關(guān)技術(shù)及實(shí)施過(guò)程。埋點(diǎn)是數(shù)據(jù)領(lǐng)域的一個(gè)專業(yè)術(shù)語(yǔ),也是互聯(lián)網(wǎng)領(lǐng)域的一個(gè)俗稱。埋點(diǎn)是產(chǎn)品數(shù)據(jù)分析的基礎(chǔ),一般用于推薦系統(tǒng)的反饋、用戶行為的監(jiān)控和分析、新功能或者運(yùn)營(yíng)活動(dòng)效果的統(tǒng)計(jì)分析等。埋點(diǎn)包含兩個(gè)重要概念:事件(event),屬性(param)
事件(event):應(yīng)用中發(fā)生了什么,例如用戶操作、系統(tǒng)事件或系統(tǒng)錯(cuò)誤。以你拍一產(chǎn)品為例,包含以下事件:enter_page(進(jìn)入頁(yè)面)、leave_page(離開(kāi)頁(yè)面)。
屬性(param):為了描述用戶群細(xì)分而定義的屬性,例如語(yǔ)言偏好或地理位置。以“進(jìn)入課后練習(xí)”事件為例,它包含如下事件屬性:enter_from(從哪個(gè)頁(yè)面來(lái)),class_id(課程id)等。
屬性值(value):屬性的維度,即行為觸發(fā)時(shí)的具體維度。例如:enter_from:home(主頁(yè))、system(系統(tǒng))等。
主流方案
無(wú)痕埋點(diǎn)(全埋點(diǎn)),利用瀏覽器或APP自帶的監(jiān)聽(tīng)方式,對(duì)用戶的瀏覽頁(yè)面、點(diǎn)擊等行為進(jìn)行收集,一般用于粗顆粒度的數(shù)據(jù)分析,例如公司的slardar
數(shù)據(jù)噪聲大,不管有用沒(méi)有,數(shù)據(jù)都會(huì)被收集
無(wú)法定制化埋點(diǎn),無(wú)法采集到指定事件和業(yè)務(wù)屬性
可供DA使用的信息較少
接入簡(jiǎn)單,幾乎無(wú)侵入,不需要額外的開(kāi)發(fā)成本
用戶操作行為收集非常完整,幾乎不會(huì)遺漏
優(yōu)點(diǎn):
缺點(diǎn):
代碼埋點(diǎn),前端開(kāi)發(fā)人員在代碼中自定義監(jiān)聽(tīng)和收集
工作量大,而且對(duì)代碼侵入性很大,后期維護(hù)也不是很方便
可以精確埋點(diǎn),具備明確的事件標(biāo)識(shí)
業(yè)務(wù)屬性非常豐富
埋點(diǎn)觸發(fā)方式可以靈活定義
DA使用更方便和精確
優(yōu)點(diǎn):
缺點(diǎn):
埋點(diǎn)sdk,sdk向外暴露上報(bào)埋點(diǎn)的接口,監(jiān)聽(tīng)和收集過(guò)程開(kāi)發(fā)人員無(wú)感知。例如公司的tea
暫時(shí)想不到
業(yè)務(wù)開(kāi)發(fā)只需關(guān)注事件標(biāo)識(shí)、業(yè)務(wù)屬性等
兼顧無(wú)痕埋點(diǎn)優(yōu)點(diǎn)和代碼埋點(diǎn)的優(yōu)勢(shì)
優(yōu)點(diǎn):
缺點(diǎn):
常見(jiàn)埋點(diǎn)屬性
通常前端是按照頁(yè)面維度統(tǒng)計(jì)埋點(diǎn)的,常見(jiàn)的事件屬性如下:
| 屬性 | 描述 |
|---|---|
| uid | 用戶id,若用戶未登陸,則返回特定標(biāo)識(shí)id |
| url | 當(dāng)前事件觸發(fā)頁(yè)面的url |
| eventTime | 觸發(fā)埋點(diǎn)的時(shí)間戳 |
| localTime | 觸發(fā)埋點(diǎn)時(shí)的用戶本地時(shí)間,使用標(biāo)準(zhǔn)YYYY-MM-DD HH:mm:ss格式表示,方便后期直接使用字符串查詢 |
| deviceType | 當(dāng)前用戶使用的設(shè)備類型,比如apple、三星、chrome等 |
| deviceId | 當(dāng)前用戶使用的設(shè)備id |
| osType | 當(dāng)前用戶使用的系統(tǒng)類型,比如windows、macos、ios、android等 |
| osVersion | 當(dāng)前用戶使用的系統(tǒng)版本 |
| appVersion | 當(dāng)前應(yīng)用版本 |
| appId | 當(dāng)前應(yīng)用id |
| extra | 自定義數(shù)據(jù),一般是序列化的字符串,且數(shù)據(jù)結(jié)構(gòu)應(yīng)保持穩(wěn)定 |
常見(jiàn)埋點(diǎn)事件
| 事件 | 上報(bào)時(shí)機(jī) | 描述 |
|---|---|---|
| 頁(yè)面停留 | 當(dāng)前頁(yè)面切換或者頁(yè)面卸載時(shí) | 記錄前一頁(yè)瀏覽時(shí)間 |
| pv | 進(jìn)入頁(yè)面時(shí) | 頁(yè)面訪問(wèn)次數(shù),uv只需要根據(jù)deviceId過(guò)濾 |
| 交互事件 | 用戶交互事件觸發(fā)時(shí) | 比如點(diǎn)擊、長(zhǎng)按等 |
| 邏輯事件 | 符合邏輯條件時(shí) | 比如登陸、跳轉(zhuǎn)頁(yè)面等 |
性能數(shù)據(jù)采集方案
目前性能指標(biāo)數(shù)據(jù)大部分來(lái)源于 window.performance API。
Performance.timing


| 參數(shù)名 | 描述 |
|---|---|
| connectEnd | HTTP(TCP) 返回瀏覽器與服務(wù)器之間的連接建立時(shí)的時(shí)間戳。如果建立的是持久連接,則返回值等同于fetchStart屬性的值。連接建立指的是所有握手和認(rèn)證過(guò)程全部結(jié)束。 |
| connectStart | HTTP(TCP) 域名查詢結(jié)束的時(shí)間戳。如果使用了持續(xù)連接(persistent connection),或者這個(gè)信息存儲(chǔ)到了緩存或者本地資源上,這個(gè)值將和 fetchStart一致。 |
| domComplete | 當(dāng)前文檔解析完成,即Document.readyState 變?yōu)?'complete'且相對(duì)應(yīng)的readystatechange 被觸發(fā)時(shí)的時(shí)間戳 |
| domContentLoadedEventEnd | 當(dāng)所有需要立即執(zhí)行的腳本已經(jīng)被執(zhí)行(不論執(zhí)行順序)時(shí)的時(shí)間戳。 |
| domContentLoadedEventStart | 當(dāng)解析器發(fā)送DOMContentLoaded 事件,即所有需要被執(zhí)行的腳本已經(jīng)被解析時(shí)的時(shí)間戳。 |
| domInteractive | 當(dāng)前網(wǎng)頁(yè)DOM結(jié)構(gòu)結(jié)束解析、開(kāi)始加載內(nèi)嵌資源時(shí)(即Document.readyState屬性變?yōu)椤癷nteractive”、相應(yīng)的readystatechange事件觸發(fā)時(shí))的時(shí)間戳。 |
| domLoading | 當(dāng)前網(wǎng)頁(yè)DOM結(jié)構(gòu)開(kāi)始解析時(shí)(即Document.readyState屬性變?yōu)椤發(fā)oading”、相應(yīng)的 readystatechange事件觸發(fā)時(shí))的時(shí)間戳。 |
| domainLookupEnd | DNS 域名查詢完成的時(shí)間。如果使用了本地緩存(即無(wú) DNS 查詢)或持久連接,則與 fetchStart 值相等 |
| domainLookupStart | DNS 域名查詢開(kāi)始的UNIX時(shí)間戳。如果使用了持續(xù)連接(persistent connection),或者這個(gè)信息存儲(chǔ)到了緩存或者本地資源上,這個(gè)值將和fetchStart一致。 |
| fetchStart | 瀏覽器準(zhǔn)備好使用HTTP請(qǐng)求來(lái)獲取(fetch)文檔的時(shí)間戳。這個(gè)時(shí)間點(diǎn)會(huì)在檢查任何應(yīng)用緩存之前。 |
| loadEventEnd | 當(dāng)load事件結(jié)束,即加載事件完成時(shí)的時(shí)間戳。如果這個(gè)事件還未被發(fā)送,或者尚未完成,它的值將會(huì)是0. |
| loadEventStart | load事件被發(fā)送時(shí)的時(shí)間戳。如果這個(gè)事件還未被發(fā)送,它的值將會(huì)是0。 |
| navigationStart | 同一個(gè)瀏覽器上一個(gè)頁(yè)面卸載(unload)結(jié)束時(shí)的時(shí)間戳。如果沒(méi)有上一個(gè)頁(yè)面,這個(gè)值會(huì)和fetchStart相同。 |
| redirectEnd | 最后一個(gè)HTTP重定向完成時(shí)(也就是說(shuō)是HTTP響應(yīng)的最后一個(gè)比特直接被收到的時(shí)間)的時(shí)間戳。如果沒(méi)有重定向,或者重定向中的一個(gè)不同源,這個(gè)值會(huì)返回0. |
| redirectStart | 第一個(gè)HTTP重定向開(kāi)始時(shí)的時(shí)間戳。如果沒(méi)有重定向,或者重定向中的一個(gè)不同源,這個(gè)值會(huì)返回0。 |
| requestStart | 返回瀏覽器向服務(wù)器發(fā)出HTTP請(qǐng)求時(shí)(或開(kāi)始讀取本地緩存時(shí))的時(shí)間戳。 |
| responseEnd | 返回瀏覽器從服務(wù)器收到(或從本地緩存讀取,或從本地資源讀取)最后一個(gè)字節(jié)時(shí)(如果在此之前HTTP連接已經(jīng)關(guān)閉,則返回關(guān)閉時(shí))的時(shí)間戳。 |
| responseStart | 返回瀏覽器從服務(wù)器收到(或從本地緩存讀?。┑谝粋€(gè)字節(jié)時(shí)的時(shí)間戳。如果傳輸層在開(kāi)始請(qǐng)求之后失敗并且連接被重開(kāi),該屬性將會(huì)被數(shù)制成新的請(qǐng)求的相對(duì)應(yīng)的發(fā)起時(shí)間 |
| secureConnectionStart | HTTPS 返回瀏覽器與服務(wù)器開(kāi)始安全鏈接的握手時(shí)的時(shí)間戳。如果當(dāng)前網(wǎng)頁(yè)不要求安全連接,則返回0。 |
| unloadEventEnd | 和 unloadEventStart 相對(duì)應(yīng),unload事件處理完成時(shí)的時(shí)間戳。如果沒(méi)有上一個(gè)頁(yè)面,這個(gè)值會(huì)返回0。 |
| unloadEventStart | 上一個(gè)頁(yè)面unload事件拋出時(shí)的時(shí)間戳。如果沒(méi)有上一個(gè)頁(yè)面,這個(gè)值會(huì)返回0。 |
常見(jiàn)性能指標(biāo)
| 指標(biāo)名 | 描述 |
|---|---|
| FP | 頁(yè)面首次繪制時(shí)間 |
| FCP | 頁(yè)面首次有內(nèi)容繪制的時(shí)間 |
| FMP | 頁(yè)面首次有效繪制時(shí)間,F(xiàn)MP >= FCP |
| TTI | 頁(yè)面完全可交互時(shí)間 |
| FID | 頁(yè)面加載階段,用戶首次交互操作的延時(shí)時(shí)間 |
| MPFID | 頁(yè)面加載階段,用戶交互操作可能遇到的最大延時(shí)時(shí)間 |
| LOAD | 頁(yè)面完全加載的時(shí)間(load 事件發(fā)生的時(shí)間) |
FP
FP (First Paint)指標(biāo)通常會(huì)反映頁(yè)面的白屏?xí)r間,而白屏?xí)r間會(huì)反映當(dāng)前 Web 頁(yè)面的網(wǎng)絡(luò)加載性能情況,當(dāng)加載性能非常良好的情況下,白屏的時(shí)間就會(huì)越短,用戶等待內(nèi)容的時(shí)間就會(huì)越短,流失的概率就會(huì)降低。該指標(biāo)可以通過(guò) performance.getEntriesByType('paint') 方法獲取 PerformancePaintTiming API 提供的打點(diǎn)信息,找到 name 為 first-paint 的對(duì)象,描述的即為 FP 的指標(biāo)數(shù)據(jù),如下圖所示:
FCP
FCP (First Contentful Paint) 為首次有內(nèi)容渲染的時(shí)間點(diǎn),在性能統(tǒng)計(jì)指標(biāo)中,從用戶開(kāi)始訪問(wèn) Web 頁(yè)面的時(shí)間點(diǎn)到 FCP 的時(shí)間點(diǎn)這段時(shí)間可以被視為無(wú)內(nèi)容時(shí)間,一般 FCP >= FP。該指標(biāo)可以通過(guò) performance.getEntriesByType('paint') 方法獲取 PerformancePaintTiming API 提供的打點(diǎn)信息,找到 name 為 first-contentful-paint 的對(duì)象,描述的即為 FCP 的指標(biāo)數(shù)據(jù),如下圖所示:
FMP
FMP(First Meaningful Paint),即首次繪制有意義內(nèi)容的時(shí)間,當(dāng)整體頁(yè)面的布局和文字內(nèi)容全部渲染完成后,即可認(rèn)為是完成了首次有意義內(nèi)容的繪制。所以 FMP 衡量了用戶看到網(wǎng)頁(yè)的主要內(nèi)容的時(shí)間,是用戶體驗(yàn)角度的一種重要的衡量指標(biāo)。前端業(yè)界現(xiàn)在比較認(rèn)可的一個(gè)計(jì)算 FMP 的方式就是「頁(yè)面在加載和渲染過(guò)程中最大布局變動(dòng)之后的那個(gè)繪制時(shí)間 」。可通過(guò) MutationObserver 監(jiān)聽(tīng)每一次頁(yè)面整體的 DOM 變化,觸發(fā) MutationObserver 的回調(diào),在回調(diào)計(jì)算出當(dāng)前 DOM 樹(shù)的變動(dòng)分?jǐn)?shù),分?jǐn)?shù)變化最劇烈的時(shí)刻,即為 FMP 的時(shí)間點(diǎn)。
TTI
TTI(Time To Interactive),即從頁(yè)面加載開(kāi)始到頁(yè)面處于完全可交互狀態(tài)所花費(fèi)的時(shí)間。頁(yè)面處于完全可交互狀態(tài)時(shí),滿足以下 3 個(gè)條件:
頁(yè)面已經(jīng)顯示有用內(nèi)容。
頁(yè)面上的可見(jiàn)元素關(guān)聯(lián)的事件響應(yīng)函數(shù)已經(jīng)完成注冊(cè)。
事件響應(yīng)函數(shù)可以在事件發(fā)生后的 50ms 內(nèi)開(kāi)始執(zhí)行。
資源加載指標(biāo)
window.performance.getEntriesByType('resource')會(huì)返回當(dāng)前頁(yè)面加載的所有資源(js、css、img...)的各類性能指標(biāo),可用于靜態(tài)資源性能數(shù)據(jù)采集。
主要類型有:script、link、img、css、xmlhttprequest、beacon、fetch、other。PerformanceResourceTiming - Web APIs | MDN
| 參數(shù)名 | 描述 |
|---|---|
| connectEnd | 一個(gè) DOMHighResTimeStamp,表示瀏覽器完成建立與服務(wù)器的連接以檢索資源之后的時(shí)間。 |
| connectStart | 一個(gè) DOMHighResTimeStamp,表示瀏覽器開(kāi)始建立與服務(wù)器的連接以檢索資源之前的時(shí)間。 |
| decodedBodySize | 一個(gè) number,表示在刪除任何應(yīng)用的內(nèi)容編碼之后,從消息主體的請(qǐng)求(HTTP 或緩存)中接收到的大?。ㄒ园宋蛔止?jié)為單位)。 |
| domainLookupEnd | 一個(gè) DOMHighResTimeStamp,表示瀏覽器完成資源的域名查找之后的時(shí)間。 |
| domainLookupStart | 一個(gè) DOMHighResTimeStamp,表示在瀏覽器立即開(kāi)始資源的域名查找之前的時(shí)間 |
| duration | 返回一個(gè) timestamp,即 responseEnd 和 startTime 屬性的差值。 |
| encodedBodySize | 一個(gè) number,表示在刪除任何應(yīng)用的內(nèi)容編碼之前,從有效內(nèi)容主體的請(qǐng)求(HTTP 或緩存)中接收到的大?。ㄒ园宋蛔止?jié)為單位)。 |
| entryType | 返回 "resource"。 |
| fetchStart | 一個(gè) DOMHighResTimeStamp,表示瀏覽器即將開(kāi)始獲取資源之前的時(shí)間。 |
| initiatorType | 一個(gè) string,代表啟動(dòng)性能條目的資源的類型 |
| name | 返回資源 URL。 |
| nextHopProtocol | 一個(gè) string,代表用于獲取資源的網(wǎng)絡(luò)協(xié)議,由 ALPN 協(xié)議 ID(RFC7301) 定義。 |
| redirectEnd | 一個(gè) DOMHighResTimeStamp,表示收到上一次重定向響應(yīng)的發(fā)送最后一個(gè)字節(jié)時(shí)的時(shí)間。 |
| redirectStart | 一個(gè) DOMHighResTimeStamp 代表啟動(dòng)重定向的請(qǐng)求開(kāi)始之前的時(shí)間。 |
| requestStart | 一個(gè) DOMHighResTimeStamp,表示瀏覽器開(kāi)始向服務(wù)器請(qǐng)求資源之前的時(shí)間。 |
| responseEnd | 一個(gè) DOMHighResTimeStamp,表示在瀏覽器接收到資源的最后一個(gè)字節(jié)之后或在傳輸連接關(guān)閉之前(以先到者為準(zhǔn))的時(shí)間。 |
| responseStart | 一個(gè) DOMHighResTimeStamp,表示瀏覽器從服務(wù)器接收到響應(yīng)的第一個(gè)字節(jié)后的時(shí)間。 |
| secureConnectionStart | 一個(gè) DOMHighResTimeStamp,表示瀏覽器即將開(kāi)始握手過(guò)程以保護(hù)當(dāng)前連接之前的時(shí)間。 |
| serverTiming | 一個(gè) PerformanceServerTiming 數(shù)組,包含服務(wù)器計(jì)時(shí)指標(biāo)的 PerformanceServerTiming 條目。 |
| startTime | 返回一個(gè) timestamp,表示資源獲取開(kāi)始的時(shí)間。該值等效于 fetchStart。 |
| transferSize | 一個(gè) number 代表所獲取資源的大?。ㄒ园宋蛔止?jié)為單位)。該大小包括響應(yīng)標(biāo)頭字段以及響應(yīng)有效內(nèi)容主體。 |
| workerStart | 一個(gè) DOMHighResTimeStamp, 如果服務(wù) Worker 線程已經(jīng)在運(yùn)行,則返回在分派 FetchEvent 之前的時(shí)間戳,如果尚未運(yùn)行,則返回在啟動(dòng) Service Worker 線程之前的時(shí)間戳。如果服務(wù) Worker 未攔截該資源,則該屬性將始終返回 0。 |
其他指標(biāo)計(jì)算方式
| 指標(biāo)名 | 描述 | 計(jì)算方式 |
|---|---|---|
| DNS查詢 | DNS 階段耗時(shí) | domainLookupEnd - domainLookupStart |
| TCP連接 | TCP 階段耗時(shí) | connectEnd - connectStart |
| SSL建連 | SSL 連接時(shí)間 | connectEnd - secureConnectionStart |
| 首字節(jié)網(wǎng)絡(luò)請(qǐng)求 | 首字節(jié)響應(yīng)時(shí)間(ttfb) | responseStart - requestStart |
| 內(nèi)容傳輸 | 內(nèi)容傳輸,Response階段耗時(shí) | responseEnd - responseStart |
| DOM解析 | Dom解析時(shí)間 | domInteractive - responseEnd |
| 資源加載 | 資源加載 | loadEventStart - domContentLoadedEventEnd |
| 首字節(jié) | 首字節(jié) | responseStart - fetchStart |
| DOM Ready | dom ready | domContentLoadedEventEnd - fetchStart |
| redirect時(shí)間 | 重定向時(shí)間 | redirectEnd - redirectStart |
| DOM render | dom渲染耗時(shí) | domComplete - domLoading |
| load | 頁(yè)面加載耗時(shí) | loadEventEnd - navigationStart |
| unload | 頁(yè)面卸載耗時(shí) | unloadEventEnd - unloadEventStart |
| 請(qǐng)求耗時(shí) | 請(qǐng)求耗時(shí) | responseEnd - requestStart |
| 白屏?xí)r間 | 白屏?xí)r間 | domLoading - navigationStart |
錯(cuò)誤數(shù)據(jù)采集方案
目前所能捕捉的錯(cuò)誤有三種:
資源加載錯(cuò)誤,通過(guò)
addEventListener('error', callback, true)在捕獲階段捕捉資源加載失敗錯(cuò)誤。js 執(zhí)行錯(cuò)誤,通過(guò)
window.onerror捕捉 js 錯(cuò)誤。跨域的腳本會(huì)給出 "Script Error." 提示,拿不到具體的錯(cuò)誤信息和堆棧信息。此時(shí)需要在script標(biāo)簽增加
crossorigin="anonymous"屬性,同時(shí)資源服務(wù)器需要增加CORS相關(guān)配置,比如Access-Control-Allow-Origin: *promise 錯(cuò)誤,通過(guò)
addEventListener('unhandledrejection', callback)捕捉 promise 錯(cuò)誤,但是沒(méi)有發(fā)生錯(cuò)誤的行數(shù),列數(shù)等信息,只能手動(dòng)拋出相關(guān)錯(cuò)誤信息。
// 在捕獲階段,捕獲資源加載失敗錯(cuò)誤
addEventListener('error', e => {
const target = e.target
if (target != window) {
monitor.errors.push({
type: target.localName,
url: target.src || target.href,
msg: (target.src || target.href) + ' is load error',
time: Date.now()
})
}
}, true)
// 監(jiān)聽(tīng) js 錯(cuò)誤
window.onerror = function(msg, url, row, col, error) {
monitor.errors.push({
type: 'javascript',
row: row,
col: col,
msg: error && error.stack? error.stack : msg,
url: url,
time: Date.now()
})
}
// 監(jiān)聽(tīng) promise 錯(cuò)誤 缺點(diǎn)是獲取不到行數(shù)數(shù)據(jù)
addEventListener('unhandledrejection', e => {
monitor.errors.push({
type: 'promise',
msg: (e.reason && e.reason.msg) || e.reason || '',
time: Date.now()
})
})
數(shù)據(jù)上報(bào)方案
在這個(gè)場(chǎng)景中,需要考慮兩個(gè)問(wèn)題:
如果數(shù)據(jù)上報(bào)接口與業(yè)務(wù)系統(tǒng)使用同一域名,瀏覽器對(duì)請(qǐng)求并發(fā)量有限制,所以存在網(wǎng)絡(luò)資源競(jìng)爭(zhēng)的可能性。
瀏覽器通常在頁(yè)面卸載時(shí)會(huì)忽略異步ajax請(qǐng)求,如果需要必須進(jìn)行數(shù)據(jù)請(qǐng)求,一般在unload或者beforeunload事件中創(chuàng)建同步ajax請(qǐng)求,以此延遲頁(yè)面卸載。從用戶側(cè)角度,就是頁(yè)面跳轉(zhuǎn)變慢。
Beacon
可以看到,除開(kāi)ie瀏覽器,目前主流現(xiàn)代瀏覽器對(duì)beacon的支持率非常高。Beacon - MDN文檔Beacon 接口用來(lái)調(diào)度向 Web 服務(wù)器發(fā)送的異步非阻塞請(qǐng)求。
Beacon 請(qǐng)求使用 HTTP
POST方法,并且不需要有響應(yīng)。Beacon 請(qǐng)求能確保在頁(yè)面觸發(fā) unload 之前完成初始化。
通俗的講就是,Beacon可將數(shù)據(jù)異步發(fā)送至服務(wù)端,且能夠保證在頁(yè)面卸載完成前發(fā)送請(qǐng)求(解決ajax頁(yè)面卸載會(huì)終止請(qǐng)求的問(wèn)題)。使用方法如下:
navigator.sendBeacon(url, data);
其中 data 參數(shù)是可選的,它的類型可以為 ArrayBufferView, Blob, DOMString 或者 FormData。如果瀏覽器成功地將 beacon 請(qǐng)求加入到待發(fā)送的隊(duì)列里,這個(gè)方法將會(huì)返回 true ,否則將會(huì)返回 false使用Beacon時(shí)需要后臺(tái)需要使用post方法接收參數(shù),考慮到跨域問(wèn)題,后臺(tái)還需要改造接口配置CORS。同時(shí)請(qǐng)求頭必須滿足CORS-safelisted request-header,其中content-type的類型必須為application/x-www-form-urlencoded, multipart/form-data, 或者text/plain。
type ContentType = 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
const serilizeParams = (params: object) => {
return window.btoa(JSON.stringify(params))
}
function sendBeacon(url: string, params: object) {
const formData = new FormData()
formData.append('params', serilizeParams(params))
navigator.sendBeacon(url, formData)
}
Image
sendBeacon的兼容性問(wèn)題是不可避免的,不過(guò)可以充分利用大部分瀏覽器會(huì)在頁(yè)面卸載前完成圖片的加載的特性,通過(guò)在頁(yè)面添加img的方式上報(bào)數(shù)據(jù)。
function sendImage(url: string, params: object) {
const img = new Image()
img.style.display = 'none'
const removeImage = function() {
img.parentNode.removeChild(img)
}
img.onload = removeImage
img.onerror = removeImage
img.src = `${url}?params=${serilizeParams(params)}`
document.body.appendChild(img)
}
由于img圖片為get請(qǐng)求方式,不同服務(wù)器針對(duì)uri的長(zhǎng)度有限制,長(zhǎng)度超過(guò)限制時(shí)會(huì)出現(xiàn)HTTP 414錯(cuò)誤,所以還要注意上報(bào)頻率,減少一次性上傳的屬性過(guò)多。
HTTP 1.1 defines Status Code 414 Request-URI Too Long for the cases where a server-defined limit is reached. You can see further details on RFC 2616. For the case of client-defined limits, there is no sense on the server returning something, because the server won't receive the request at all.
兼容方案
優(yōu)先使用sendBeacon的方式,Image方式作為fallback。
function sendLog(url: string, params: object) {
if(navigator.sendBeacon) {
sendBeacon(url, params)
} else {
sendImage(url, params)
}
}
本文作者:隨風(fēng)丶逆風(fēng)
本文鏈接:https://juejin.cn/post/6938075086737899534
