作者:九思
你好,我是九思,来自腾讯前端技术部,擅长前端监控、工程化相关技术。此篇文章将围绕前端性能优化中监控问题展开讨论。
首先我们要知道,页面打开快不快,不是在电脑或手机上的打开速度说了算,也不是测试同学测试的结果说了算,而是真实用户使用的时候说了算。
那么如何去监控用户真实使用时的页面性能呢?本文将做细致介绍(建议收藏)
页面上线后,我们最关心用户打开页面的速度,通常就是首屏。
页面加载离不开静态资源的加载,包括 js、css、img、video、font 等,在如今盛行 SPA 的场景尤为重要,比如活动页面会有很多图片,我们通常会开发一些模板,由产品/运营同学来配置,而图片多大合适是比较难确定的,网速越来越快,大家对清晰度要求也越来越高,此时就可以通过监测这些图片的加载速度来酌情优化。
数据是页面中相当重要的元素,可以说没有数据,你的页面几乎没有使用价值(纯静态除外)。当然这里我们只能粗暴的监控整个请求的总时间,纯前端无法监控各个阶段时间,但这对于线上应用也很重要。
实际上线后,不同的应用可能会有不同的测速诉求,比如:视频从加载到播放的时间,此时可以自定义一些测速点,利用之前讲述的打点方式来上报。
这块其实还是比较简单的,只需要利用 PerformanceResourceTiming 即可,并且它的兼容性极高,可以覆盖到几乎所有场景。
实际监控时,可以分两种场景,如果支持 performanceObserver 可以实时监听,否则使用定时器方式,此外需要将此代码放到页面最顶层,否则无法监控到这段代码之前的资源加载。
const typeList = \['script', 'link', 'img'\]; // const staticTime = {}; const dealTime = (entries) => { for (let i = 0, l = entries.length; i < l; i++) { const entry = entries\[i\]; if (typeList.indexOf(entry.initiatorType) !== -1) { staticTime\[entry.name\] = entry.connectEnd - entry.connectStart; } } } if (typeof window.PerformanceObserver === 'function') { const observer = new window.PerformanceObserver((list) => { dealTime(list.getEntries()); }); observer.observe({ entryTypes: \['resource'\] }); } else { setInterval(() => { const allEntries = performance.getEntriesByType('resource'); const entries = allEntries.slice(allEntries.length); dealTime(entries); }, 5000); }
entry 部分数值
connectEnd: 32.63499999593478
connectStart: 32.63499999593478
decodedBodySize: 160302
domainLookupEnd: 32.63499999593478
domainLookupStart: 32.63499999593478
duration: 37.54000000481028
encodedBodySize: 23876
entryType: "resource"
fetchStart: 32.63499999593478
initiatorType: "link"
name: "https://stackpath.bootstrapcdn.com
nextHopProtocol: "h2"
redirectEnd: 0
redirectStart: 0
requestStart: 44.60999999719206
responseEnd: 70.17500000074506
responseStart: 65.40999999560881
secu reConnectionStart: 32.63499999593478
serverTiming:[]
startTime: 32.63499999593478
transferSize: 23971
workerStart: 0
关于 API 的监控,可以采用重写 XHR 或者 fetch,这样可以适用任何的框架、请求库。时间计算则是通过打点的方式,如果只是固定项目监控,也可以直接采用打点的方式。如果采用重写的方式,不仅可以监控请求的时长,还可以监控请求的成功失败率。
打点最简单的方式:
const startTime = Date.now(); fn() // 假设这里是同步执行 const timeCycle = Date.now() - startTime; // fn的执行耗时
除了以上这几点,前端监控的实操中我们还应该考虑到上报、限流以及如何处理这些性能数据。
发送请求我们很容易想到适用 fetch / XHR,当然也可以使用自动发起请求的 HTML 标签,比如 script、link、img。上报数据虽然可以拿来分析页面的真实运行数据,但有一点要注意的是:不能影响当前页面的运行或最小程度的影响。
是否可以直接用 fetch/XHR 呢?答案是否定的,因为上报的域名和页面的域名基本是不同的,所以这里需要可以前端跨域的方式。
说到跨域,浏览器的 src 属性标签基本都可以,到底用哪个呢?原则上要适用对页面影响最小的那个,诸如 Script、link 这些标签之前有讲述,他们都会对页面的运行造成影响。
而 img 变成了较为合适的方式,构造图片打点不仅不用插入DOM,只要在 JS 中 new 出 Image 对象就能发起请求,而且还没有阻塞问题,在没有js的浏览器环境中也能通过 img 标签正常打点,这是其他类型的资源请求所做不到的。
在所有图片中1px x 1px 大小,gif 体积最小,相较 BMP/PNG,可以节约41%/35%的网络资源,所以适用 gif 相对是最佳选择。
页面的性能数据,每次访问都会有,如果你的项目 pv 有一定量级,那么处理起来就会相当耗费资源,而且这些数据我们最终是求平均值或者分位值,所以没必要全量上报。那么我们可以在上报前做一些限流处理。
除了以上这几点,前端监控的实操中我们还应该考虑到如何处理这些性能数据,主要有取中位数、平均值、分位数等做法,由于篇幅有限此处不做详细介绍。
最后,如果这篇文章给你带来些许有价值的理解,欢迎点赞、分享,更多内容可翻阅我的付费专栏《前端性能优化12问》。