最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 教你编写简易的性能检测工具

    正文概述 掘金(夏天Summer)   2021-02-18   636

    前言

    性能优化一直是我们需要注意的问题,尤其是对于C端产品来说更是重中之重。而关于性能优化的文章教材层出不穷,这里不过多赘述。而对于性能优化过后,性能到底提升了多少,我们需要有一个很直观的数据,才能更加方便我们工作,也能更清楚的计算出我们所获得的成果。

    效果展示: 教你编写简易的性能检测工具

    源码地址: pmat

    现有工具

    现在的性能检测方式非常多

    • Chrome 自带的开发者工具:Performance
    • Lighthouse 开源工具
    • 原生 Performance API
    • 各种官方库、插件

    这些工具都各有各的优点,但同样都有一定的局限性。比如 Lighthouse 可以用可视化各类指标来非常直观的看到各项检测数据,但是却无法完成一些特定的需求,比如有的项目需要登录才能看到具体的页面等等...

    而我们需要做的是,结合他们的一些特点,整合成我们所需要的东西,而且对于甚至不了解原生 Performance API这块知识的同学来说,是个非常好的学习跟实践的机会。

    准备知识

    这里在开始做工具之前,需要了解原生 Performance API知识点。

    这里直接推荐 2018你应该知道的Web性能信息采集指南, 虽然时间有些久了,但看完基本上对这块已经有了些基本了解,这里就不赘述了。

    想要什么样的工具

    这里表明下我的思路其实就是结合了社区大佬写的 per-moniteur 和 hiper

    • per-moniteur: 项目内注入js, 利用 PerformanceObserver 监听页面性能,在控制台输出结果,主要监测 FCP, LCP等性能指标
    • hiper: 命令行工具,利用 puppeteer启动无头浏览器多次启动输入网址即可返回平均监测数据, 不过只计算了 performance.timing(已过时)的数据

    所以我的想法就是将它两者结合起来,即只要输入网址和请求次数,即可测试到它的性能指标(包括基础的检测数据以及性能指标数据),也可根据puppeteer扩展缓存设置等配置。

    开始

    工具全部使用typescript开发,发布前转换成commonjs语法,这也没有什么特别,基本所有人都会。

    ○ 开始入口

    class Pmat {
      // 命令入口,解析参数
      public cli: Cli;
      // puppeteer 启动无头浏览器
      public puppeteer: Puppeteer;
      // 性能检测对象数组
      public observer: Observer;
    
      constructor() {
        this.cli = new Cli();
        this.puppeteer = new Puppeteer();
        this.observer = new Observer();
      }
    
      async run() {
        // 获取命令返回的参数
        const options = await this.cli.monitor();
        // 初始化无头浏览器
        const puppeteer = await this.puppeteer.init(options);
    
        ......
    
        const { count, url } = options;
        const { page, browser } = puppeteer;
    
        // listr2 创建任务
        const task = new Listr([
          {
            title: 'start executing',
            task: async () => {
              // 根据输入的打开的页面次数,进行检测
              for (let i = 0; i < count; i += 1) {
                // 执行生命周期 beforeStart
                await this.observer.beforeStart();
                await page.goto(url, { waitUntil: 'load' });
                // 开始检测
                await this.observer.start();
              }
            },
          },
          {
            title: 'start calculating',
            task: async () => {
              // 根据检测的值进行计算,获取平均数
              await this.observer.calculate();
            },
          },
        ]);
    
        ......
      }
    }
    

    上面代码就是启动入口的操作,即根据命令的输入参数进行相应的操作。

    教你编写简易的性能检测工具

    ○ 创建无头浏览器

    import puppeteer from 'puppeteer';
    
    import type { IPuppeteerOutput } from './interface';
    import type { ICliOptions } from '../cli/interface';
    
    class Puppeteer {
      async init(options?: ICliOptions): Promise<IPuppeteerOutput> {
      
        const browser = await puppeteer.launch({
          product: 'chrome',
        });
        const page = await browser.newPage();
    
        // 获取命令行参数
        const { cache, javascript, online, useragent, tti } = options;
    
        // 设置每个请求忽略缓存
        await page.setCacheEnabled(cache);
        // 是否启用js
        await page.setJavaScriptEnabled(javascript);
        // 是否启用离线模式
        await page.setOfflineMode(!online);
    
        if (useragent) {
          await page.setUserAgent(useragent);
        }
    
        return { page, browser, tti };
      }
    }
    
    export default Puppeteer;
    

    这里就是简单的使用 puppteer 创建无头浏览器,并根据命令行输入的内容设置浏览器属性。更多api的使用可以参照 官方文档

    计算性能

    ○ 计算 navigation

    navigation 是指通过 Performance接口获取的关于浏览器文档事件的指标的方法和属性。具体方法是 performance.getEntriesByType('navigation'), 便可获得类似以下格式的数据:

    教你编写简易的性能检测工具

    而其中具体的计算方式可以参考:

    教你编写简易的性能检测工具

    通过以上知识,就可以很简单地计算出我们想要的数据,计算方式如下:

    总时长:duration,
    Redirect: redirectEnd - redirectStart,
    AppCache: domainLookupStart - fetchStart,
    DNS: domainLookupEnd - domainLookupStart,
    TCP: connectEnd - connectStart,
    First Byte time: responseStart - requestStart,
    Download: responseEnd - responseStart,
    白屏时间: domInteractive - fetchStart,
    DOMReady: domContentLoadedEventEnd - fetchStart,
    Load: domContentLoadedEventEnd - domContentLoadedEventStart
    

    注意:因为工具是多次请求一个地址,所以需要计算所有数据总和的平均值

    ○ 计算性能指标

    为了能够更具体的量化性能优化这一块,我们需要获取谷歌提出的这一系列性能指标,但由于谷歌一直在更新性能优化的指标,以下的思维导图算是目前最新的一些性能指标,其中一些性能指标的计算不一定完全准确,只供参考,更详细的内容可以看还在看那些老掉牙的性能优化文章么?这些最新性能指标了解下

    教你编写简易的性能检测工具

    而我们如何通过代码去计算这些性能指标呢,主要是通过 PerformanceObserver 来获取数据,可以把它看成是一个监听器用来收集所需要的性能指标,主要格式如下所示:

    const perfObserver = new PerformanceObserver((entryList) => {
        // 信息处理
    })
    
    // 传入需要的 type
    perfObserver.observe({ entryTypes: ['paint'] })
    

    FP & FCP

    • FP(First Paint),首次绘制,这个指标用于记录页面第一次绘制像素的时间。

    • FCP(First Contentful Paint),首次内容绘制,这个指标用于记录页面首次绘制文本、图片、非空白 Canvas 或 SVG 的时间。

    page.evaluateOnNewDocument(getPaint);
    
    function getPaint() {
      window.FP = 0;
      window.FCP = 0;
    
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          const { startTime, name } = entry;
          if (name === 'first-contentful-paint') {
            window.FCP = startTime;
          } else {
            window.FP = startTime;
          }
        }
      });
    
      observer.observe({ entryTypes: ['paint'] });
    }
    

    LCP

    • LCP(Largest Contentful Paint),最大内容绘制,用于记录视窗内最大的元素绘制的时间
    await page.evaluateOnNewDocument(calcLCP);
    await page.goto(url, { waitUntil: 'load', timeout: 60000 });
    
    let lcp = await page.evaluate(() => {
        return window.largestContentfulPaint;
    });
    
    function calcLCP() {
    	window.largestContentfulPaint = 0;
    
        const observer = new PerformanceObserver((entryList) => {
            const entries = entryList.getEntries();
            const lastEntry = entries[entries.length - 1];
            window.largestContentfulPaint = lastEntry.renderTime || lastEntry.loadTime;
        });
    
        observer.observe({ type: 'largest-contentful-paint', buffered: true });
    
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'hidden') {
                observer.takeRecords();
                observer.disconnect();
                console.log('LCP:', window.largestContentfulPaint);
            }
        });
    }
    

    CLS

    • CLS(Cumulative Layout Shift),累计位移偏移,记录了页面上非预期的位移波动。
    window.CLS = 0;
    
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) {
          window.CLS += entry.value;
        }
      }
    });
    
    observer.observe({ entryTypes: ['layout-shift'] });
    

    TTI

    • TTI(Time to Interactive),首次可交互时间。通俗来讲就是页面从 FCP 到可以点击交互的时间,这个指标非常的重要,基本上就标志了页面的性能。而它的计算需要符合下面的几个条件:
    1. FCP 指标后开始计算
    2. 持续 5 秒内无长任务(执行时间超过 50 ms)且无两个以上正在进行中的 GET 请求
    3. 往前回溯至 5 秒前的最后一个长任务结束的时间

    TTI的计算使用了 tti-polyfill

    window.__tti = { e: [] };
    
    const observer = new PerformanceObserver((list) => {
      const fcp = performance.getEntriesByName('first-contentful-paint')[0].startTime;
      const entries = list.getEntries();
    
      window.__tti.e = window.__tti.e.concat(entries);
    })
    observer.observe({ entryTypes: ['longtask'] });
    
    ...
    
    await page.addScriptTag({ path: './node_modules/tti-polyfill/tti-polyfill.js' });
    
    // Time to Interactive
    TTI = await page.evaluate(() =>
      window.ttiPolyfill ? window.ttiPolyfill.getFirstConsistentlyInteractive() : -1,
    );
    

    FID

    • FID(First Input Delay),首次输入延迟,记录在 FCP 和 TTI 之间用户首次与页面交互时响应的延迟。
    window.FID = 0;
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        window.FID = entry.processingStart - entry.startTime;
      }
    });
    
    observer.observe({ type: 'first-input', buffered: true });
    

    TBT

    • TBT(Total Blocking Time),阻塞总时间,记录在 FCP 到 TTI 之间所有长任务的阻塞时间总和。
    window.TBT = 0;
    
    const observer = new PerformanceObserver((list) => {
      const fcp = performance.getEntriesByName('first-contentful-paint')[0].startTime;
      const entries = list.getEntries();
    
      for (const entry of entries) {
        if (entry.name !== 'self' || entry.startTime < fcp) {
          return;
        }
        // long tasks mean time over 50ms
        const blockingTime = entry.duration - 50;
        if (blockingTime > 0) window.TBT += blockingTime;
      }
    });
    
    observer.observe({ entryTypes: ['longtask'] });
    

    最后

    平时空闲的时候写了这个工具,一是为了学习性能检测方面的知识点,二是为了在之后对项目进行性能优化的时候能够更直观的看到优化的幅度。有问题的地方欢迎指正。感谢。


    起源地下载网 » 教你编写简易的性能检测工具

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元