地 址:聯係地址聯係地址聯係地址
电 话:020-123456789
网址:www.6ql2.cn
邮 箱:[email protected]
對於後台開發來說 ,刷新刷新記錄日誌是当前当前一種非常常見的開發習慣,通常我們會使用try...catch代碼塊來主動捕獲錯誤 、页面页面對於每次接口調用,白屏也會記錄下每次接口調用的满满時間消耗,以便我們監控服務器接口性能,干货進行問題排查 。刷新刷新
點擊上方藍字,記得關注我們!本文5240字 ,页面页面預計閱讀需要24分鍾
對於後台開發來說 ,記錄日誌是满满一種非常常見的開發習慣,通常我們會使用try...catch代碼塊來主動捕獲錯誤、干货對於每次接口調用,刷新刷新也會記錄下每次接口調用的当前当前時間消耗 ,以便我們監控服務器接口性能 ,页面页面進行問題排查 。
剛進公司時,在進行Node.js的接口開發時 ,我不太習慣每次排查問題都要通過跳板機登上服務器看日誌,後來慢慢習慣了這種方式舉個例子:/** * 獲取列表數據 * @parma req, res */exports.getList =
asyncfunction (req, res) { //獲取請求參數const openId = req.session.userinfo.openId; logger.info(`handler getList, user openId is
${ openId}`);try { // 拿到列表數據const startTime = newDate().getTime();let res = await ListService.getListFromDB(openId);
logger.info(`handler getList, ListService.getListFromDB cost time ${ newDate().getTime() - startDate}
`);// 對數據處理 ,返回給前端// ... } catch(error) { logger.error(`handler getList is error, ${ JSON.stringify(error)}
`); }};以下代碼經常會出現在用Node.js的接口中 ,在接口中會統計查詢DB所耗時間、亦或是統計RPC服務調用所耗時間 ,以便監測性能瓶頸,對性能做優化;又或是對異常使用try ... catch主動捕獲 ,以便隨時對問題進行回溯 、還原問題的場景 ,進行bug的修複 。
而對於前端來說呢 ?可以看以下的場景最近在進行一個需求開發時 ,偶爾發現webgl渲染影像失敗的情況,或者說影像會出現解析失敗的情況 ,我們可能根本不知道哪張影像會解析或渲染失敗;又或如最近開發的另外一個需求 ,我們會做一個關於webgl渲染時間的優化和影像預加載的需求 ,如果缺乏性能監控 ,該如何統計所做的渲染優化和影像預加載優化的優化比例 ,如何證明自己所做的事情具有價值呢?可能是通過測試同學的黑盒測試,對優化前後的時間進行錄屏,分析從進入頁麵到影像渲染完成到底經過了多少幀圖像。
這樣的數據,可能既不準確、又較為片麵,設想測試同學並不是真正的用戶 ,也無法還原真實的用戶他們所處的網絡環境回過頭來發現 ,我們的項目 ,雖然在服務端層麵做好了日誌和性能統計 ,但在前端對異常的監控和性能的統計。
對於前端的性能與異常上報的可行性探索是有必要的異常捕獲方法對於前端來說,我們需要的異常捕獲無非為以下兩種 :接口調用情況;頁麵邏輯是否錯誤,例如,用戶進入頁麵後頁麵顯示白屏;對於接口調用情況,在前端通常需要上報客戶端相關參數,例如:用戶OS與瀏覽器版本 、請求參數(如頁麵ID);而對於頁麵邏輯是否錯誤問題 ,通常除了用戶OS與瀏覽器版本外 ,需要的是報錯的堆棧信息及具體報錯位置 。
異常捕獲方法全局捕獲可以通過全局監聽異常來捕獲,通過window.onerror或者addEventListener ,看以下例子:window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error
) { console.log(errorMessage: + errorMessage); // 異常信息console.log(scriptURI: + scriptURI); // 異常文件路徑
console.log(lineNo: + lineNo); // 異常行號console.log(columnNo: + columnNo); // 異常列號console.log(error:
+ error); // 異常堆棧信息// ...// 異常上報};thrownewError(這是一個錯誤);
通過window.onerror事件,可以得到具體的異常信息 、異常文件的URL、異常的行號與列號及異常的堆棧信息 ,再捕獲異常後,統一上報至我們的日誌服務器亦或是,通過window.addEventListener方法來進行異常上報,道理同理 :。
window.addEventListener(error, function() { console.log(error);// ...// 異常上報});thrownewError(這是一個錯誤);
try... catch使用try... catch雖然能夠較好地進行異常捕獲,不至於使得頁麵由於一處錯誤掛掉 ,但try ... catch捕獲方式顯得過於臃腫,大多代碼使用try ... catch包裹,影響代碼可讀性。
常見問題跨域腳本無法準確捕獲異常通常情況下 ,我們會把靜態資源,如JavaScript腳本放到專門的靜態資源服務器 ,亦或者CDN,看以下例子 :
>// 在index.htmlwindow.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error
) { console.log(errorMessage: + errorMessage); // 異常信息console.log(scriptURI: + scriptURI); // 異常文件路徑
console.log(lineNo: + lineNo); // 異常行號console.log(columnNo: + columnNo); // 異常列號console.log(error:
+ error); // 異常堆棧信息// ...// 異常上報 };// error.js
thrownewError(這是一個錯誤);
結果顯示,跨域之後window.onerror根本捕獲不到正確的異常信息,而是統一返回一個Script error,解決方案:對script標簽增加一個crossorigin=”anonymous” ,並且服務器添加Access-Control-Allow-Origin。
sourceMap通常在生產環境下的代碼是經過webpack打包後壓縮混淆的代碼,所以我們可能會遇到這樣的問題,如圖所示:
我們發現所有的報錯的代碼行數都在第一行了,為什麽呢?這是因為在生產環境下 ,我們的代碼被壓縮成了一行:!function(e){ var n={ };functionr(o){ if(n[o])return n[o].exports;
var t=n[o]={ i:o,l:!1,exports:{ }};return e[o].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=e,r.c=n,r.d=
function(e,n,o){ r.o(e,n)||Object.defineProperty(e,n,{ enumerable:!0,get:o})},r.r=function(e){ "undefined"
!=typeofSymbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{ value:"Module"}),Object
.defineProperty(e,"__esModule",{ value:!0})},r.t=function(e,n){ if(1&n&&(e=r(e)),8&n)return e;if(4&n&&"object"
==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default"
,{ enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var t in e)r.d(o,t,function(n){ return e[n]}.bind(
null,t));return o},r.n=function(e){ var n=e&&e.__esModule?function(){ return e.default}:function(){ return
e};return r.d(n,"a",n),n},r.o=function(e,n){ returnObject.prototype.hasOwnProperty.call(e,n)},r.p="",r(r.s=
0)}([function(e,n){ throwwindow.onerror=function(e,n,r,o,t){ console.log("errorMessage: "+e),console.log(
"scriptURI: "+n),console.log("lineNo: "+r),console.log("columnNo: "+o),console.log("error: "+t);var l={
errorMessage:e||null,scriptURI:n||null,lineNo:r||null,columnNo:o||null,stack:t&&t.stack?t.stack:null};
if(XMLHttpRequest){ var u=new XMLHttpRequest;u.open("post","/middleware/errorMsg",!0),u.setRequestHeader(
"Content-Type","application/json"),u.send(JSON.stringify(l))}},newError("這是一個錯誤")}]);在我的開發過程中也遇到過這個問題,我在開發一個功能組件庫的時候,使用npm link了我的組件庫 ,但是由於組件庫被npm link後是打包後的生產環境下的代碼 ,所有的報錯都定位到了第一行。
解決辦法是開啟webpack的source-map,我們利用webpack打包後的生成的一份.map的腳本文件就可以讓瀏覽器對錯誤位置進行追蹤了此處可以參考webpack document其實就是webpack.config.js中加上一行devtool: source-map,如下所示 ,為示例的webpack.config.js:。
var path = require(path);module.exports = { devtool: source-map,mode: development,entry: ./client/index.js
,output: { filename: bundle.js,path: path.resolve(__dirname, client) }}在webpack打包後生成對應的source-map ,這樣瀏覽器就能夠定位到具體錯誤的位置 :
開啟source-map的缺陷是兼容性,目前隻有Chrome瀏覽器和Firefox瀏覽器才對source-map支持不過我們對這一類情況也有解決辦法可以使用引入npm庫來支持source-map,可以參考mozilla/source-map 。
這個npm庫既可以運行在客戶端也可以運行在服務端 ,不過更為推薦的是在服務端使用Node.js對接收到的日誌信息時使用source-map解析 ,以避免源代碼的泄露造成風險 ,如下代碼所示:const express =
require(express);const fs = require(fs);const router = express.Router();const sourceMap = require(source-map
);const path = require(path);const resolve = file => path.resolve(__dirname, file);// 定義post接口router.get(
/error/, asyncfunction(req, res) { // 獲取前端傳過來的報錯對象let error = JSON.parse(req.query.error);let url = error.scriptURI;
// 壓縮文件路徑if (url) { let fileUrl = url.slice(url.indexOf(client/)) + .map; // map文件路徑// 解析sourceMaplet consumer =
awaitnew sourceMap.SourceMapConsumer(fs.readFileSync(resolve(../ + fileUrl), utf8)); // 返回一個promise對象
// 解析原始報錯數據let result = consumer.originalPositionFor({ line: error.lineNo, // 壓縮後的行號 column: error.columnNo
// 壓縮後的列號 });console.log(result); }});module.exports = router;如下圖所示 ,我們已經可以看到,在服務端已經成功解析出了具體錯誤的行號、列號 ,我們可以通過日誌的方式進行記錄 ,達到了前端異常監控的目的。
Vue捕獲異常在我的項目中就遇到這樣的問題 ,使用了js-tracker這樣的插件來統一進行全局的異常捕獲和日誌上報,結果發現我們根本捕獲不到Vue組件的異常 ,查閱資料得知,在Vue中 ,異常可能被Vue自身給try ... catch了 ,不會傳到window.onerror事件觸發 ,那麽我們如何把Vue組件中的異常作統一捕獲呢 ?
使用Vue.config.errorHandler這樣的Vue全局配置,可以在Vue指定組件的渲染和觀察期間未捕獲錯誤的處理函數這個處理函數被調用時,可獲取錯誤信息和Vue 實例Vue.config.errorHandler = 。
function (err, vm, info) { // handle error// `info` 是 Vue 特定的錯誤信息 ,比如錯誤所在的生命周期鉤子// 隻在 2.2.0+ 可用}在React中,可以使用ErrorBoundary組件包括業務組件的方式進行異常捕獲,配合React 16.0+新出的componentDidCatch API ,可以實現統一的異常捕獲和日誌上報 。
classErrorBoundaryextendsReact.Component{ constructor(props) { super(props);this.state = { hasError: false
}; } componentDidCatch(error, info) { // Display fallback UIthis.setState({ hasError: true });// You can also log the error to an error reporting service
logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI
return
性能監控最簡單的性能監控最常見的性能監控需求則是需要我們統計用戶從開始請求頁麵到所有DOM元素渲染完成的時間,也就是俗稱的首屏加載時間 ,DOM提供了這一接口 ,監聽document的DOMContentLoaded事件與window的load事件可統計頁麵首屏加載時間即所有DOM渲染時間:
// 記錄頁麵加載開始時間var timerStart =
Date.now();
document.addEventListener(DOMContentLoaded, function() { console.log("DOM 掛載時間: ", Date.now() - timerStart);
// 性能日誌上報 });window.addEventListener(load, function() { console.log("所有資源加載完成時間: ", Date.now()-timerStart);
// 性能日誌上報 });對於使用框架,如Vue或者說React,組件是異步渲染然後掛載到DOM的 ,在頁麵初始化時並沒有太多的DOM節點,可以參考下文關於首屏時間采集自動化的解決方案來對渲染時間進行打點。
performance但是以上時間的監控過於粗略,例如我們想統計文檔的網絡加載耗時 、解析DOM的耗時與渲染DOM的耗時,就不太好辦到了 ,所幸的是瀏覽器提供了window.performance接口,具體可見MDN文檔
幾乎所有瀏覽器都支持window.performance接口 ,下麵來看看在控製台打印window.performance可以得到些什麽 :
可以看到 ,window,performance主要包括有memory、navigation 、timing以及timeOrigin及onresourcetimingbufferfull方法navigation對象提供了在指定的時間段裏發生的操作相關信息,包括頁麵是加載還是刷新 、發生了多少次重定向等等 。
timing對象包含延遲相關的性能信息這是我們頁麵加載性能優化需求中主要上報的相關信息memory為Chrome添加的一個非標準擴展,這個屬性提供了一個可以獲取到基本內存使用情況的對象在其它瀏覽器應該考慮到這個API的兼容處理。
timeOrigin則返回性能測量開始時的時間的高精度時間戳如圖所示,精確到了小數點後四位onresourcetimingbufferfull方法,它是一個在resourcetimingbufferfull事件觸發時會被調用的event handler 。
這個事件當瀏覽器的資源時間性能緩衝區已滿時會觸發可以通過監聽這一事件觸發來預估頁麵crash,統計頁麵crash概率,以便後期的性能優化,如下示例所示:functionbuffer_full(event
) { console.log("WARNING: Resource Timing Buffer is FULL!"); performance.setResourceTimingBufferSize(
200);}functioninit() { // Set a callback if the resource buffer becomes filled performance.onresourcetimingbufferfull = buffer_full;
}計算網站性能使用performance的timing屬性 ,可以拿到頁麵性能相關的數據,這裏在很多文章都有提到關於利用window.performance.timing記錄頁麵性能的文章,例如alloyteam團隊寫的初探 performance – 監控網頁與程序性能 ,對於timing的各項屬性含義 ,可以借助摘自此文的下圖理解 ,以下代碼摘自此文作為計算網站性能的工具函數參考:
// 獲取 performance 數據var performance = { // memory 是非標準屬性 ,隻在 Chrome 有// 財富問題 :我有多少內存 memory: { usedJSHeapSize:
16100000, // JS 對象(包括V8引擎內部對象)占用的內存 ,一定小於 totalJSHeapSize totalJSHeapSize: 35100000, // 可使用的內存 jsHeapSizeLimit:
793000000// 內存大小限製 },// 哲學問題:我從哪裏來 ? navigation: { redirectCount: 0, // 如果有重定向的話,頁麵通過幾次重定向跳轉而來
type: 0// 0 即 TYPE_NAVIGATENEXT 正常進入的頁麵(非刷新 、非重定向等)// 1 即 TYPE_RELOAD 通過 window.location.reload() 刷新的頁麵
// 2 即 TYPE_BACK_FORWARD 通過瀏覽器的前進後退按鈕進入的頁麵(曆史記錄)// 255 即 TYPE_UNDEFINED 非以上方式進入的頁麵 }, timing: {
// 在同一個瀏覽器上下文中,前一個網頁(與當前頁麵不一定同域)unload 的時間戳 ,如果無前一個網頁 unload ,則與 fetchStart 值相等 navigationStart:
1441112691935,// 前一個網頁(與當前頁麵同域)unload 的時間戳 ,如果無前一個網頁 unload 或者前一個網頁與當前頁麵不同域 ,則值為 0 unloadEventStart:
0,// 和 unloadEventStart 相對應 ,返回前一個網頁 unload 事件綁定的回調函數執行完畢的時間戳 unloadEventEnd: 0,// 第一個 HTTP 重定向發生時的時間。
有跳轉且是同域名內的重定向才算,否則值為 0 redirectStart: 0,// 最後一個 HTTP 重定向完成時的時間有跳轉且是同域名內部的重定向才算 ,否則值為 0 redirectEnd: 。
0,// 瀏覽器準備好使用 HTTP 請求抓取文檔的時間,這發生在檢查本地緩存之前 fetchStart: 1441112692155,// DNS 域名查詢開始的時間 ,如果使用了本地緩存(即無 DNS 查詢)或持久連接,則與 fetchStart 值相等
domainLookupStart: 1441112692155,// DNS 域名查詢完成的時間 ,如果使用了本地緩存(即無 DNS 查詢)或持久連接 ,則與 fetchStart 值相等
domainLookupEnd: 1441112692155,// HTTP(TCP) 開始建立連接的時間,如果是持久連接 ,則與 fetchStart 值相等// 注意如果在傳輸層發生了錯誤且重新建立連接 ,則這裏顯示的是新建立的連接開始的時間
connectStart: 1441112692155,// HTTP(TCP) 完成建立連接的時間(完成握手),如果是持久連接,則與 fetchStart 值相等// 注意如果在傳輸層發生了錯誤且重新建立連接 ,則這裏顯示的是新建立的連接完成的時間
// 注意這裏握手結束,包括安全連接建立完成 、SOCKS 授權通過 connectEnd: 1441112692155,// HTTPS 連接開始的時間,如果不是安全連接 ,則值為 0 secureConnectionStart:
0,// HTTP 請求讀取真實文檔開始的時間(完成建立連接),包括從本地讀取緩存// 連接錯誤重連時,這裏顯示的也是新建立連接的時間 requestStart: 1441112692158
,// HTTP 開始接收響應的時間(獲取到第一個字節),包括從本地讀取緩存 responseStart: 1441112692686,// HTTP 響應全部接收完成的時間(獲取到最後一個字節) ,包括從本地讀取緩存
responseEnd: 1441112692687,// 開始解析渲染 DOM 樹的時間 ,此時 Document.readyState 變為 loading,並將拋出 readystatechange 相關事件
domLoading: 1441112692690,// 完成解析 DOM 樹的時間,Document.readyState 變為 interactive,並將拋出 readystatechange 相關事件
// 注意隻是 DOM 樹解析完成 ,這時候並沒有開始加載網頁內的資源 domInteractive: 1441112693093,// DOM 解析完成後,網頁內資源加載開始的時間// 在 DOMContentLoaded 事件拋出前發生
domContentLoadedEventStart: 1441112693093,// DOM 解析完成後,網頁內資源加載完成的時間(如 JS 腳本加載執行完畢) domContentLoadedEventEnd:
1441112693101,// DOM 樹解析完成,且資源也準備就緒的時間 ,Document.readyState 變為 complete,並將拋出 readystatechange 相關事件 domComplete:
1441112693214,// load 事件發送給文檔 ,也即 load 回調函數開始執行的時間// 注意如果沒有綁定 load 事件,值為 0 loadEventStart: 1441112693214
,// load 事件的回調函數執行完畢的時間 loadEventEnd: 1441112693215// 字母順序// connectEnd: 1441112692155,// connectStart: 1441112692155,
// domComplete: 1441112693214,// domContentLoadedEventEnd: 1441112693101,// domContentLoadedEventStart: 1441112693093,
// domInteractive: 1441112693093,// domLoading: 1441112692690,// domainLookupEnd: 1441112692155,// domainLookupStart: 1441112692155,
// fetchStart: 1441112692155,// loadEventEnd: 1441112693215,// loadEventStart: 1441112693214,// navigationStart: 1441112691935,
// redirectEnd: 0,// redirectStart: 0,// requestStart: 1441112692158,// responseEnd: 1441112692687,// responseStart: 1441112692686,
// secureConnectionStart: 0,// unloadEventEnd: 0,// unloadEventStart: 0 }};// 計算加載時間functiongetPerformanceTiming
() { var performance = window.performance;if (!performance) { // 當前瀏覽器不支持console.log(你的瀏覽器不支持 performance 接口
);return; }var t = performance.timing;var times = { };//【重要】頁麵加載完成的時間//【原因】這幾乎代表了用戶等待頁麵可用的時間 times.loadPage = t.loadEventEnd - t.navigationStart;
//【重要】解析 DOM 樹結構的時間//【原因】反省下你的 DOM 樹嵌套是不是太多了! times.domReady = t.domComplete - t.responseEnd;//【重要】重定向的時間
//【原因】拒絕重定向!比如,http://example.com/ 就不該寫成 http://example.com times.redirect = t.redirectEnd - t.redirectStart;
//【重要】DNS 查詢時間//【原因】DNS 預加載做了麽?頁麵內是不是使用了太多不同的域名導致域名查詢的時間太長?// 可使用 HTML5 Prefetch 預查詢 DNS ,見:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)
times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;//【重要】讀取頁麵第一個字節的時間//【原因】這可以理解為用戶拿到你的資源占用的時間 ,加異地機房了麽,加CDN 處理了麽 ?加帶寬了麽?加 CPU 運算速度了麽 ?
// TTFB 即 Time To First Byte 的意思// 維基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte times.ttfb = t.responseStart - t.navigationStart;
//【重要】內容加載完成的時間//【原因】頁麵內容經過 gzip 壓縮了麽,靜態資源 css/js 等壓縮了麽 ? times.request = t.responseEnd - t.requestStart;
//【重要】執行 onload 回調函數的時間//【原因】是否太多不必要的操作都放到 onload 回調函數裏執行了,考慮過延遲加載、按需加載的策略麽 ? times.loadEvent = t.loadEventEnd - t.loadEventStart;
// DNS 緩存時間 times.appcache = t.domainLookupStart - t.fetchStart;// 卸載頁麵的時間 times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;
// TCP 建立連接完成握手的時間 times.connect = t.connectEnd - t.connectStart;return times;}日誌上報單獨的日誌域名對於日誌上報使用單獨的日誌域名的目的是避免對業務造成影響。
其一 ,對於服務器來說,我們肯定不希望占用業務服務器的計算資源 ,也不希望過多的日誌在業務服務器堆積 ,造成業務服務器的存儲空間不夠的情況其二 ,我們知道在頁麵初始化的過程中 ,會對頁麵加載時間、PV 、UV等數據進行上報,這些上報請求會和加載業務數據幾乎是同時刻發出,而瀏覽器一般會對同一個域名的請求量有並發數的限製,如Chrome會有對並發數為6個的限製。
因此需要對日誌係統單獨設定域名 ,最小化對頁麵加載性能造成的影響跨域的問題對於單獨的日誌域名 ,肯定會涉及到跨域的問題 ,采取的解決方案一般有以下兩種:一種是構造空的Image對象的方式 ,其原因是請求圖片並不涉及到跨域的問題;
var url = xxx;new Image().src = url;利用Ajax上報日誌,必須對日誌服務器接口開啟跨域請求頭部Access-Control-Allow-Origin:*,這裏Ajax就並不強製使用GET請求了,即可克服URL長度限製的問題。
if (XMLHttpRequest) { var xhr = new XMLHttpRequest(); xhr.open(post, https://log.xxx.com, true); // 上報給node中間層處理
xhr.setRequestHeader(Content-Type, application/json); // 設置請求頭 xhr.send(JSON.stringify(errorObj));
// 發送參數}在我的項目中使用的是第一種的方式,也就是構造空的Image對象,但是我們知道對於GET請求會有長度的限製 ,需要確保的是請求的長度不會超過閾值省去響應主體對於我們上報日誌,其實對於客戶端來說,並不需要考慮上報的結果,甚至對於上報失敗 ,我們也不需要在前端做任何交互,所以上報來說,其實使用HEAD請求就夠了,接口返回空的結果,最大地減少上報日誌造成的資源浪費。
合並上報類似於雪碧圖的思想 ,如果我們的應用需要上報的日誌數量很多,那麽有必要合並日誌進行統一的上報解決方案可以是嚐試在用戶離開頁麵或者組件銷毀時發送一個異步的POST請求來進行上報 ,但是嚐試在卸載(unload)文檔之前向web服務器發送數據 。
保證在文檔卸載期間發送數據一直是一個困難因為用戶代理通常會忽略在卸載事件處理器中產生的異步XMLHttpRequest,因為此時已經會跳轉到下一個頁麵所以這裏是必須設置為同步的XMLHttpRequest請求嗎? 。
window.addEventListener(unload, logData, false);functionlogData() { var client = new XMLHttpRequest();
client.open("POST", "/log", false); // 第三個參數表明是同步的 xhr client.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8"); client.send(analyticsData);}使用同步的方式勢必會對用戶體驗造成影響,甚至會讓用戶感受到瀏覽器卡死感覺 ,對於產品而言,體驗非常不好 ,通過查閱MDN文檔,可以使用sendBeacon()方法 ,將會使用戶代理在有機會時異步地向服務器發送數據,同時不會延遲頁麵的卸載或影響下一導航的載入性能。
這就解決了提交分析數據時的所有的問題:使它可靠,異步並且不會影響下一頁麵的加載此外 ,代碼實際上還要比其他技術簡單 !下麵的例子展示了一個理論上的統計代碼模式——通過使用sendBeacon()方法向服務器發送數據。
window.addEventListener(unload, logData, false);functionlogData() { navigator.sendBeacon("/log", analyticsData);
}小結作為前端開發者而言,要對產品保持敬畏之心,時刻保持對性能追求極致 ,對異常不可容忍的態度前端的性能監控與異常上報顯得尤為重要代碼難免有問題,對於異常可以使用window.onerror或者addEventListener的方式添加全局的異常捕獲偵聽函數 ,但可能使用這種方式無法正確捕獲到錯誤 :對於跨域的腳本 ,需要對script標簽增加一個crossorigin=”anonymous”;對於生產環境打包的代碼 ,無法正確定位到異常產生的行數,可以使用source-map來解決;而對於使用框架的情況 ,需要在框架統一的異常捕獲處埋點。
而對於性能的監控,所幸的是瀏覽器提供了window.performance API,通過這個API ,很便捷地獲取到當前頁麵性能相關的數據而這些異常和性能數據如何上報呢?一般說來,為了避免對業務產生的影響 ,會單獨建立日誌服務器和日誌域名,但對於不同的域名 ,又會產生跨域的問題。
我們可以通過構造空的Image對象來解決 ,亦或是通過設定跨域請求頭部Access-Control-Allow-Origin:*來解決此外 ,如果上報的性能和日誌數據高頻觸發,則可以在頁麵unload時統一上報 ,而unload時的異步請求又可能會被瀏覽器所忽略,且不能改為同步請求。
此時navigator.sendBeacon API可算幫了我們大忙,它可用於通過HTTP將少量數據異步傳輸到Web服務器而忽略頁麵unload時的影響就醬 ,下期再見
免責聲明:本站所有信息均搜集自互聯網,並不代表本站觀點,本站不對其真實合法性負責。如有信息侵犯了您的權益,請告知,本站將立刻處理 。聯係QQ:1640731186