摘要:本文的介紹的是如何設計一個通用的可以以較小的侵入性,自動上報前端的性能數據。具體的做法可以看我的上一篇文章在單頁應用中,如何優雅的監聽的變化。三如何上報性能數據那么如何上報性能數據呢,我們第一反應就是通過請求的形式來上報前端性能數據。
??最近在做一個較為通用的前端性能監控平臺,區別于前端異常監控,前端的性能監控主要需要上報和展示的是前端的性能數據,包括首頁渲染時間、每個頁面的白屏時間、每個頁面所有資源的加載時間以及每一個頁面中所以請求的響應時間等等。
??本文的介紹的是如何設計一個通用的jssdk,可以以較小的侵入性,自動上報前端的性能數據。主要采用的是Performance API以及sendBeacon方法等等。主要參考的是google analytics以及阿里云前端性能監控平臺的實踐。
??在我的項目中使用nestjs作為后端框架,nestjs是基于express的一款完美支持typescript,類java spring的node后端框架。本文主要側重與如何上報性能數據,后端處理邏輯比較簡單,不會具體介紹,因此不需要了解如何使用nestjs。本文的主要內容包含了:
根據Performance API獲取前端性能數據
何時應該上報性能數據
如何上報性能數據
原文在我的博客中,歡迎star
https://github.com/forthealll...
一、根據Performance API 獲取前端性能數據本文上報的前端性能數據包含兩部分,一是通過Performance API獲得的性能數據,二是自定義的在每個頁面應該上報的數據。
首先來看通過Performance API所獲取的數據,該數據也包含了兩個部分,當前頁面的性能相關數據以及當前頁面資源加載和異步請求的相關數據。
(1)、Performance API 所提供的性能數據window.performance.timing會返回一個對象,該對象包含了各種與頁面渲染所相關的數據。本文不會具體去介紹該對象,只給出根據該對象計算相關性能數據的方法:
let times = {}; let t = window.performance.timing; //重定向時間 times.redirectTime = t.redirectEnd - t.redirectStart; //dns查詢耗時 times.dnsTime = t.domainLookupEnd - t.domainLookupStart; //TTFB 讀取頁面第一個字節的時間 times.ttfbTime = t.responseStart - t.navigationStart; //DNS 緩存時間 times.appcacheTime = t.domainLookupStart - t.fetchStart; //卸載頁面的時間 times.unloadTime = t.unloadEventEnd - t.unloadEventStart; //tcp連接耗時 times.tcpTime = t.connectEnd - t.connectStart; //request請求耗時 times.reqTime = t.responseEnd - t.responseStart; //解析dom樹耗時 times.analysisTime = t.domComplete - t.domInteractive; //白屏時間 times.blankTime = t.domLoading - t.fetchStart; //domReadyTime times.domReadyTime = t.domContentLoadedEventEnd - t.fetchStart;
在上面的times對象中就包含了性能相關的屬性,根據performance.timing中的相關屬性計算就可以得到結果。在這里我們認為domReadyTime就是首屏加載的時間,此外也可以自定義的方法上報首屏的時間:
比如有些場景可以認為是dom增量最大的點為首屏渲染完成的時間,也有一些場景可以定義可見的dom在增量最大處為首屏渲染完成的時間。
(2)、Performance API 所提供的資源加載和請求數據??可以通過window.performance.getEntries()來獲取資源的加載和請求相關的數據。每一個頁面中,需要去加載很多資源比如js、css等等,同時在頁面中還會存在一些異步請求。通過window.performance.getEntries()可以獲得這些資源加載和異步請求所相關的數據。我們可以通過如下的方式來獲取加載和異步請求的數據:
let entryTimesList = []; let entryList = window.performance.getEntries(); entryList.forEach((item,index)=>{ let templeObj = {}; let usefulType = ["navigation","script","css","fetch","xmlhttprequest","link","img"]; if(usefulType.indexOf(item.initiatorType)>-1){ templeObj.name = item.name; templeObj.nextHopProtocol = item.nextHopProtocol; //dns查詢耗時 templeObj.dnsTime = item.domainLookupEnd - item.domainLookupStart; //tcp鏈接耗時 templeObj.tcpTime = item.connectEnd - item.connectStart; //請求時間 templeObj.reqTime = item.responseEnd - item.responseStart; //重定向時間 templeObj.redirectTime = item.redirectEnd - item.redirectStart; entryTimesList.push(templeObj); } });
我們通過window.performance.getEntries()獲得一個帶有資源加載和異步請求相關數據的數組,然后根據數組中每一個元素的initiatorType屬性來過濾出屬性為["navigation","script","css","fetch","xmlhttprequest","link","img"]之一的元素數據。
(3)、注意點通過window.performance.timing所獲的的頁面渲染所相關的數據,在單頁應用中改變了url但不刷新頁面的情況下是不會更新的。因此如果僅僅通過該api是無法獲得每一個子路由所對應的頁面渲染的時間。如果需要上報切換路由情況下每一個子頁面重新render的時間,需要自定義上報。
通過window.performance.getEntries()所獲取的資源加載和異步請求所相關的數據,在頁面切換路由的時候會重新的計算,可以實現自動的上報。
二、何時上報性能數據??接著來確定應該何時上報性能數據,因為要處理pv(訪問量)和uv(獨立用戶訪問量),一般認為一次上報就是一次訪問,那么何時上報性能數據呢。在我的系統中選擇在一下場景下進行一次前端性能數據的上報:
頁面加載和重新刷新
頁面切換路由
頁面所在的tab標簽重新變得可見
針對上述的3種場景,特別是切換路由的情況,如果切換路由是通過改變hash值來實現的,那么只需要監聽hashchange事件,如果是通過html5的history api來改變url的,那么需要重新定義pushstate和replacestate事件。具體的做法可以看我的上一篇文章:在單頁應用中,如何優雅的監聽url的變化。
直接給出history實現路由場景下監聽url改變的方案:
var _wr = function(type) { var orig = history[type]; return function() { var rv = orig.apply(this, arguments); var e = new Event(type); e.arguments = arguments; window.dispatchEvent(e); return rv; }; }; history.pushState = _wr("pushState"); history.replaceState = _wr("replaceState");
然后我們就可以根據上述場景,分別監聽相應的事件,從而實現前端性能數據的上報:
addEvent(window,"load",function(e){ ...deal with something }); //監控history基礎上實現的單頁路由中url的變化 addEvent(window,"replaceState", function(e) { ...deal with something }); addEvent(window,"pushState", function(e) { ...deal with something }); //通過hash切換來實現路由的場景 addEvent(window,"hashchange",function(e){ ...deal with something }); addEvent("document","visibilitychang",function(e){ ...deal with something })
addEvent是一個兼容IE和標準DOM事件流模型的事件。
三、如何上報性能數據??那么如何上報性能數據呢,我們第一反應就是通過ajax請求的形式來上報前端性能數據。這種方法有一些缺陷,比如必須對跨域做特殊處理以及如果頁面銷毀后,相應的ajax方法并不一定發送成功等問題。
其中跨域的問題比較好處理,最難解決的問題是第二點:
就是如果頁面銷毀,那么對應的ajax方法并不一定能成功發送。
??我們可以根據google analytics(GA)中的方法,根據瀏覽器的兼容性以及url的長度,來采用不同的方法上報性能數據,主要原理是:
通過動態創建img標簽的方式,在img.src中拼接url的方式發送請求,不存在跨域限制。如果url太長,則才用sendBeacon的方式發送請求,如果sendBeacon方法不兼容,則發送ajax post同步請求
(1)、sendBeacon方法??解決在文檔卸載或者頁面關閉后無法完成異步ajax請求的問題,很多情況下我們會把異步變成同步。在頁面卸載的unload或者beforeunload事件中執行同步方法調用。
但是同步方法調用存在一個問題,就是會推遲A頁面切換進入B頁面的時間。而sendBeacon方法解決了該問題,簡單來說:
sendBeacon方法在頁面銷毀期,可以異步的發送數據,因此不會造成類似同步ajax請求那樣的阻塞問題,也不會影響下一個頁面的渲染
sendBeacon的調用方式為:
navigator.sendBeacon(url [, data]);
data可以為: ArrayBufferView, Blob, DOMString, 或者 FormData
為了發送參數,我們一般data制定為Blob的形式。此外還要注意的是,在sendBeacon的請求頭header中,不支持Content-Type為“application/json; charset=utf-8”。
在sendBeacon的header中,只支持一下3種形式的Content—Type:
application/x-www-form-urlencoded
multipart/form-data
text/plain
一般制定為application/x-www-form-urlencoded,完整的通過sendBeacon來發送請求的例子如下:
function sendBeacon(url,data){ //判斷支不支持navigator.sendBeacon let headers = { type: "application/x-www-form-urlencoded" }; let blob = new Blob([JSON.stringify(data)], headers); navigator.sendBeacon(url,blob); }
后端如何處理sendBeacon請求呢,sendBeacon在的請求頭中發送的是一個類似與POST的請求,因此可以類似于處理post一樣來處理sendBeacon請求。
一般我們約定ajax請求的content—type為:“application/json; charset=utf-8”,而sendBeacon請求的content-type為:“application/x-www-form-urlencoded”,這樣在后端處理中,就可以區別是正常的ajax post請求還是sendBeacon請求。
此外,在處理請求的時候如果存在跨域問題,通過cors跨域的方式來處理,后端需要配置:allow-control-allow-origin等,可以通過express的cors包,來簡化配置:
async function bootstrap() { const app = await NestFactory.create(ApplicationModule,instance); app.use(cors()); await app.listen(3000) } bootstrap();(2)動態創建img標簽的形式
??通過動態創建img標簽的形式,指定src屬性所指定的url來發送請求,首先不受跨域的限制,其次img標簽動態插入,會延遲頁面的卸載保證圖片的插入,因此可以保證在頁面的銷毀期,請求可以發生。
下面是一個動態創建img標簽的例子:
function imgReport(url, data) { if (!url || !data) { return; } let image = document.createElement("img"); let items = []; items = JSON.Parse(data); let name = "img_" + (+new Date()); image.onload = image.onerror = function () { }; let newUrl = url + (url.indexOf("?") < 0 ? "?" : "&") + items.join("&"); image.src = newUrl; }
此外,我們在動態創建img標簽發送請求的時候,請求的是一張圖片,在后端處理的時候,要在末尾將這個圖片返回,這樣前端的image.onload方法才會被觸發。我們以請求的地址為:localhost:8080/1.jpg為例,后端的處理邏輯為:
@Controller("1.jpg") export class AppUploadController { constructor(private readonly appService: AppService) {} @Get() getUpload(@Req() req,@Res() res): void { ...deal with some thing res.sendFile(join(__dirname, "..", "public/1.jpg")) } }
在get請求的處理中,我們通過res.sendFile(join(__dirname, "..", "public/1.jpg"))將圖片返回后,這樣前端的image的onload方法才會被調用。
(3)同步ajax post請求??動態創建img標簽的方法,拼接url的時候存在一定的問題,因為瀏覽器對url的長度是有限制的。而sendBeacon方法兼容性不是很好,最后兜底的處理方式就是發送同步的ajax請求,同步的ajax請求前面說過,會在頁面銷毀期之前執行,雖然會有一定程度的阻塞下一個頁面的渲染。
function xmlLoadData(url,data) { var client = new XMLHttpRequest(); client.open("POST", url,false); client.setRequestHeader("Content-Type", "application/json; charset=utf-8"); client.send(JSON.stringify(data)); }(4)綜合解決方案
??一般首先拼接攜帶參數的完整的url,判斷url的長度,如果url的長度小于瀏覽器允許的最大長度內,那么通過動態創建img標簽的形式來發送前端性能數據,如果url太長,則判斷瀏覽器是否支持sendBeacon方法,如果支持,則通過sendBeacon方法來發送請求,否則發送同步的ajax請求。
function dealWithUrl(url,appId){ let times = performanceInfo(appId); let items = decoupling(times); let urlLength = (url + (url.indexOf("?") < 0 ? "?" : "&") + items.join("&")).length; if(urlLength<2083){ imgReport(url,times); }else if(navigator.sendBeacon){ sendBeacon(url,times); }else{ xmlLoadData(url,times); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101000.html
摘要:本文的介紹的是如何設計一個通用的可以以較小的侵入性,自動上報前端的性能數據。具體的做法可以看我的上一篇文章在單頁應用中,如何優雅的監聽的變化。三如何上報性能數據那么如何上報性能數據呢,我們第一反應就是通過請求的形式來上報前端性能數據。 ??最近在做一個較為通用的前端性能監控平臺,區別于前端異常監控,前端的性能監控主要需要上報和展示的是前端的性能數據,包括首頁渲染時間、每個頁面的白屏時...
摘要:本文的介紹的是如何設計一個通用的可以以較小的侵入性,自動上報前端的性能數據。具體的做法可以看我的上一篇文章在單頁應用中,如何優雅的監聽的變化。三如何上報性能數據那么如何上報性能數據呢,我們第一反應就是通過請求的形式來上報前端性能數據。 ??最近在做一個較為通用的前端性能監控平臺,區別于前端異常監控,前端的性能監控主要需要上報和展示的是前端的性能數據,包括首頁渲染時間、每個頁面的白屏時...
摘要:單頁應用的原理從早起的根據的變化,到根據的的變化,實現無刷新條件下的頁面重新渲染。那么在單頁應用中是如何監聽的變化呢,本文將總結一下,如何在單頁頁面中優雅的監聽的變化。在下幾章中,重點介紹一下如何監聽的改變。 ??單頁應用的原理從早起的根據url的hash變化,到根據H5的history的變化,實現無刷新條件下的頁面重新渲染。那么在單頁應用中是如何監聽url的變化呢,本文將總結一下,...
摘要:單頁應用的原理從早起的根據的變化,到根據的的變化,實現無刷新條件下的頁面重新渲染。那么在單頁應用中是如何監聽的變化呢,本文將總結一下,如何在單頁頁面中優雅的監聽的變化。在下幾章中,重點介紹一下如何監聽的改變。 ??單頁應用的原理從早起的根據url的hash變化,到根據H5的history的變化,實現無刷新條件下的頁面重新渲染。那么在單頁應用中是如何監聽url的變化呢,本文將總結一下,...
閱讀 786·2021-11-11 16:54
閱讀 1517·2021-08-24 10:01
閱讀 1911·2019-08-30 15:54
閱讀 3296·2019-08-29 14:02
閱讀 3129·2019-08-28 18:22
閱讀 2244·2019-08-28 18:09
閱讀 3698·2019-08-26 10:26
閱讀 2664·2019-08-23 18:23