|
前言前端页面性能对用户留存、用户直观体验有着重要作用。这样的话如何更好的监控前端页面性能就变的十分重要。前端页面的性能监控主要分为两个方式:一种叫做 合成监控SyntheticMonitoring,SYN。就是在一个模拟场景里,提交一个需要做性能审计的页面,通过一系列的工具、规则去运行页面,提取一些性能指标,得出一个审计报告。合成监控中最近比较流行的是Google的Lighthouse另一种是 真实用户监控RealUserMonitoring,RUM。监控真实的用户访问数据,上报数据到服务器,然后经过数据清洗加工,得到最终的性能数据。在前端性能监控中有一个非常重要的指标就是首屏时间,因为首屏时间直接反应了用户多久能看到页面的主要内容,这决定了用户体验。这样的话,如何取到准确的首屏时间对我们来说就变的非常重要。本文就结合之前的实践,聊一聊首屏时间如何计算。Performance在SSR(服务端渲染)的应用中,我们认为html的body渲染完成的时间就是首屏时间。我们通常使用W3C标准的Performance对象来计算首屏时间。Performance经常被用于采集性能数据,因为对象内置了几乎所有常用前端需要的性能参数。imagePerformance包含了四个属性:memory、navigation、timeOrigin、timing,以及一个事件处理程序onresourcetimingbufferfull。下面我们简单介绍一下Performance的api。memorymemory这个属性提供了一个可以获取到基本内存使用情况的对象MemoryInfoperformance.memory = { jsHeapSizeLimit, // 内存大小限制,单位是字节B totalJSHeapSize, // 可使用的内存大小,单位是字节B usedJSHeapSize // JS对象占用的内存大小,单位是字节B}navigation返回PerformanceNavigation对象,提供了在指定的时间段发生的操作相关信息,包括页面是加载还是刷新、发生了多少重定向等。performance.navigation = { redirectCount: '', type: ''}timeOrigin返回性能测量开始的时间的高精度时间戳timing返回PerformanceTiming对象,包含了各种与浏览器性能相关的数据,提供了浏览器处理页面的各个阶段的耗时。下面是常用时间点计算window.onload = function() { var timing = performance.timing; console.log('准备新页面时间耗时: ' + timing.fetchStart - timing.navigationStart); console.log('redirect 重定向耗时: ' + timing.redirectEnd - timing.redirectStart); console.log('Appcache 耗时: ' + timing.domainLookupStart - timing.fetchStart); console.log('unload 前文档耗时: ' + timing.unloadEventEnd - timing.unloadEventStart); console.log('DNS 查询耗时: ' + timing.domainLookupEnd - timing.domainLookupStart); console.log('TCP连接耗时: ' + timing.connectEnd - timing.connectStart); console.log('request请求耗时: ' + timing.responseEnd - timing.requestStart); console.log('白屏时间: ' + timing.responseStart - timing.navigationStart); console.log('请求完毕至DOM加载: ' + timing.domInteractive - timing.responseEnd); console.log('解释dom树耗时: ' + timing.domComplete - timing.domInteractive); console.log('从开始至load总耗时: ' + timing.loadEventEnd - timing.navigationStart);}通过上面的介绍,我们可以轻松的得到首屏时间domLoadedTime = timing.domContentLoadedEventStart - timing.navigationStartFMP但是随着Vue和React等前端框盛行,导致Performance无法准确的监控到页面的首屏时间。因为页面的body是空,浏览器需要先加载js,然后再通过js来渲染页面内容。那我们使用什么数据来当做首屏时间呢?在Lighthouse中我们可以得到FMP值,FMP(全称FirstMeaningfulPaint,翻译为首次有效绘制)表示页面的主要内容开始出现在屏幕上的时间点,它是我们测量用户加载体验的主要指标。我们可以认为FMP的值就是首屏时间,但是浏览器并没有把FMP的数据提供出来。那我们如何计算呢?整个计算流程分为两个下面两个部分:1、监听元素加载,主要是为了计算Dom的分数2、计算分数的曲率,计算出最终的FMP值初始化监听initObserver() { try { if (this.supportTiming()) { this.observer = new MutationObserver(() => { let time = Date.now() - performance.timing.fetchStart; let bodyTarget = document.body; if (bodyTarget) { let score = 0; score += calculateScore(bodyTarget, 1, false); SCORE_ITEMS.push({ score, t: time }); } else { SCORE_ITEMS.push({ score: 0, t: time }); } }); } this.observer.observe(document, { childList: true, subtree: true }); if (document.readyState === "complete") { this.mark = 'readyState'; this.calFinallScore(); } else { window.addEventListener( "load", () => { this.mark = 'load'; this.calFinallScore(); }, true ); window.addEventListener( 'beforeunload', () => { this.mark = 'beforeunload'; this.calFinallScore(); }, true ) const that = this; function listenTouchstart() { if(Date.now() > 2000) { that.calFinallScore(); this.mark = 'touch'; window.removeEventListener('touchstart', listenTouchstart, true); } } window.addEventListener( 'touchstart', listenTouchstart, true ) } } catch (error) {}}我们通过MutationObserver来监听Dom的变化,然后计算当前时刻Dom的分数。有人可能会问,如果Dom每一次变化,都进行监听,是不是会特别消耗页面的性能?其实MutationObserver在执行回调时是批量执行,有些类似Vue等前端框架的渲染过程。计算分数function calculateScore(el, tiers, parentScore) { try { let score = 0; const tagName = el.tagName; if ("SCRIPT" !== tagName & "STYLE" !== tagName & "META" !== tagName & "HEAD" !== tagName) { const childrenLen = el.children ? el.children.length : 0; if (childrenLen > 0) for (let childs = el.children, len = childrenLen - 1; len >= 0; len--) { score += calculateScore(childs[len], tiers + 1, score > 0); } if (score = fmps[o - 1].t) { let l = fmps[o].score - fmps[o - 1].score; (!record || record.rate
|
|