基于performance手写一个前端首屏性能分析工具

2,942 阅读6分钟

前言

话不多说,先看效果:

是不是首屏数据一目了然~

当然 f12-performance也可以查看很多性能相关数据(本文暂不概述)

快速通道:直接划到底,CV 代码至项目根目录文件即可尝鲜~

window.performance的作用

是浏览器暴露给js的一个接口,可以通过这个接口查看用户访问网站的连接建立时间、dns时间等信息。window.performance属性则可以获得更为精确的原始数据,以毫秒为单位,精确到微秒。使用该api时需要在页面完全加载完成之后才能使用,最简单的办法是在window.onload事件中读取各种数据,因为很多值必须在页面完全加载之后才能得出。

window.performance.timing属性介绍

上图

window.performance.timing的执行顺序图

按触发顺序排列所有属性:

navigationStart:在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等

unloadEventStart:前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0

unloadEventEnd:和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳

redirectStart:第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0

redirectEnd:最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内的重定向才算,否则值为 0

fetchStart:浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前

domainLookupStart:DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等

domainLookupEnd:DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等

connectStart:HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间

connectEnd:HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间

注意: 这里握手结束,包括安全连接建立完成、SOCKS 授权通过

secureConnectionStart:HTTPS 连接开始的时间,如果不是安全连接,则值为 0

requestStart:HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存,连接错误重连时,这里显示的也是新建立连接的时间

responseStart:HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存

responseEnd:HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存

domLoading:开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件

domInteractive:完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件

注意: 只是 DOM 树解析完成,这时候并没有开始加载网页内的资源

domContentLoadedEventStart:DOM 解析完成后,网页内资源加载开始的时间,文档发生 DOMContentLoaded事件的时间

domContentLoadedEventEnd:DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕),文档的DOMContentLoaded 事件的结束时间

domComplete:DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件

loadEventStart:load 事件发送给文档,也即 load 回调函数开始执行的时间,如果没有绑定 load 事件,值为 0

loadEventEnd:load 事件的回调函数执行完毕的时间,如果没有绑定 load 事件,值为 0

performance.timing和PerformanceNavigationTiming的区别

当我兴致勃勃的在项目中使用performance.timing coding时会提示timing 已弃用(???黑人问号,那还玩个锤子!整半天你们都准备扔掉了。)

那就先简单的看看新的替代方案吧:Level 2的PerformanceNavigationTiming

PerformanceNavigationTiming的获取方式:

1、window.performance.getEntriesByType(navigation)[0]

2、window.performance.getEntries()[0]

对比

window.performance.timing:

PerformanceNavigationTiming:

通过对比我们可以看到:

  • performance.timing单位是时间戳(ms 级别)
  • PerformanceNavigationTiming的单位是相对于文档开始的时间
  • 其次PerformanceNavigationTiming中的数据会更全,精度更准确一些
  • PerformanceNavigationTiming(Navigation Timing Level2)的兼容性可能还存在问题

再回过头仔细看下 W3C(W3C Editor's Draft

emm。。。

既然如此我们接下来再补充下PerformanceNavigationTiming吧

PerformanceNavigationTiming

PerformanceNavigationTiming的执行顺序图(Navigation Timing Level2模型)

(更详细标准的解释请参看:W3C Editor's Draft)

PerformanceNavigationTiming属性简介

大多属性和上文的performance.timing基本类似,由上图可以看出PerformanceNavigationTiming中没有 navigationStart,取而代之的是 startTime(其实就是0),而其他属性不再以navigationStart为偏移值,而是以startTime 为偏移值。

duration 就是loadEventEnd 和 startTime 之间的差值,也就是我们的页面 load 总耗时。

常用计算性能指标

网页重定向的耗时:redirectEnd - redirectStart

检查本地缓存的耗时: domainLookupStart - fetchStart

DNS查询的耗时:domainLookupEnd - domainLookupStart

TCP连接的耗时:connectEnd - connectStart

从客户端发起请求到接收到响应的时间 / TTFB:responseStart - fetchStart

首次渲染时间/白屏时间:responseStart - pnt.startTime

下载服务端返回数据的时间:responseEnd - responseStart

request请求耗时:responseEnd - requestStart

解析dom树耗时:domComplete - domInteractive

dom加载完成的时间:domContentLoadedEventEnd

页面load的总耗时:duration

代码

可以 cv 至自己的项目根文件下试试

// performance
const times = () => {
  const performance = window.performance;
  if (performance) {
    setTimeout(() => { //异步获取, 同步获取时duration等值可能获取不到
      const pnt: any = performance.getEntriesByType('navigation')[0]
      console.log(pnt, 'performance');

      const column = [
        {
          key: 'Redirect',
          desc: '网页重定向的耗时',
          value: pnt.redirectEnd - pnt.redirectStart,
        },
        {
          key: 'AppCache',
          desc: '检查本地缓存的耗时',
          value: pnt.domainLookupStart - pnt.fetchStart,
        },
        {
          key: 'DNS',
          desc: 'DNS查询的耗时',
          value: pnt.domainLookupEnd - pnt.domainLookupStart,
        },
        {
          key: 'TCP',
          desc: 'TCP连接的耗时',
          value: pnt.connectEnd - pnt.connectStart,
        },
        {
          key: 'Waiting(TTFB)',
          desc: '从客户端发起请求到接收到响应的时间 / Time To First Byte',
          value: pnt.responseStart - pnt.fetchStart,
        },
        {
          key: '白屏时间',
          desc: '首次渲染时间/白屏时间',
          value: pnt.responseStart - pnt.startTime,
        },
        {
          key: 'Content Download',
          desc: '下载服务端返回数据的时间',
          value: pnt.responseEnd - pnt.responseStart,
        },
        {
          key: 'request',
          desc: 'request请求耗时',
          value: pnt.responseEnd - pnt.requestStart,
        },
        {
          key: 'dom树',
          desc: '解析dom树耗时',
          value: pnt.domComplete - pnt.domInteractive,
        },
        {
          key: 'DOMContentLoaded',
          desc: 'dom加载完成的时间',
          value: pnt.domContentLoadedEventEnd,
        },
        {
          key: 'Loaded',
          desc: '页面load的总耗时',
          value: pnt.duration
        },
      ];
      // tslint:disable-next-line:no-console
      console && console.table && console.table(column);
    }, 0)
  }
}
window.addEventListener('load', times); // onload 事件触发