前端性能优化

719 阅读8分钟

1. 6大测量指标优化策略排序

  • Largest Contentful Paint 【简称LCP: 最大内容渲染】 FCP最大内容渲染时间标记了渲染出最大文本或图片的时间
  • Total Blocking Time 【简称TBT: 总阻塞时间】 TBT测量了FCP(首次内容渲染)和TTI(可交互时间)之间的总耗时。TTI可能会被主线程阻塞以至于无法及时响应用户。大于50ms的任务称为长任务,当任意长任务出现时,主线程则称为被阻塞状态。由于浏览器不会打断正在进行中的长任务,所以,如果用户在执行长任务时和页面有交互事件时,浏览器必须等到该长任务完成才能响应。TBT计算的是在FCP到TTI之间所有长任务时间内总和。

如下图所示,我们有5个任务,其中有3个是长任务,因为它们的时长超过了50ms。 虽然运行任务的总时长是560ms但是只有345ms被认为是总阻塞时长。

  • First Contentful Paint 【简称FCP: 首次内容渲染】 FCP测量了从页面开始加载到页面任意部分的内容渲染到屏幕上。
  • Speed Index 【简称SI: 速度指数】 SI速度指数表明了网页内容的可见填充速度。lighthouse首先捕获页面加载的视屏,然后对比帧与帧之间视觉效果变化(通过计算结构相似指数SSMI来比较)。 如何提升SI呢?
  1. 最小化主线程工作量
  2. 减小JS的执行时间
  3. 确保在字体加载过程中文本可见
  • Time to Interactive 【简称TTI: 可交互时间】 可交互时间是指网页需要多长时间才能提供完整交互功能。TTI测量了从页面开始加载到页面的主要附属资源加载完毕,并且可以足够快速回应用户输入的所用时间。
  • Cumulative Layout Shift 【简称CLS: 累积布局偏移】 CLS累积布局偏移旨在测量可见元素在视口内的移动情况。CLS值越小越好。

2. 针对各个测量指标的优化措施

措施1:优化LCP

优化弹出框,替换过大背景图片。对图片做preload。加快图片下载。

措施2:减少无用JS、CSS,减小TBT,提升SI

  1. 彻底code split
  2. 前端路由懒加载
  3. split chunk(比如antd,antd-mobile等)
  4. 微前端预加载
  5. 借助Webpack Bundle Analyzer帮助我们分析项目

措施3:首图图片减小自定义首屏时间

  1. 缺省页svg转base64
  2. base64走磁盘缓存

措施4:资源预加载

这里可以使用webpack插件来打包时增加preload属性

<!-- preload js -->
<link rel="preload" as="script" href="{js地址}" crossorigin />
<!-- preload css -->
<link rel="preload" as="style" href="{css地址}" crossorigin />
<!-- preload 图片 -->
<link rel="preload" as="image" href="{图片地址}" crossorigin />

// webpack
new PreloadWebpackPlugin({
    rel:"preload",
    as(entry){
        if(/\.css$/.test(entry)) return "style"
        if(/\.woff$/.test(entry)) return "font"
        if(/\.png$/.test(entry)) return "image"
        return "script"
    }
})

措施5:提前建立第三方链接

注意:只对一些立即用到的第三方站点做这种preconnect处理,要省着用preconnect。 用preconnect给所有的第三方站点都做提前链接很低效,可以为重要的第三方站点做preconnect,其余的都做dns-prefetch. 可节约20-120ms。

<!-- preconnect 3rd party -->
<link rel="preconnect" href="https://example.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com" />
<link rel="dns-prefetch" href="https://cm.g.doubleclick.net" />

措施6:减少CLS

其实布局有偏移还是很常见的。比如我们的用户信息数据,在我的页面的用户信息卡片里,是异步接口获取的。所以看到可能会是页面先加载了一部分,然后拿到数据后进度条显示并生成进度条。这种异步的内容减少CLS的方式:

可以想办法把这个异步接口合并到主接口里来解决,或者设置一个默认的图片占位。等数据来了再替换掉。 useSWR:异步更新缓存策略,可参考深入浅出 useSWR 原理 - 知乎 (zhihu.com)

措施7:浏览器缓存机制

结合强缓存和协商缓存,合理地缓存静态资源,比如当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。

其他措施

当把lighthouse给出的opportunities和diagnosis都做到了以后,可考虑

减少服务端TTFB,需要后端优化主接口。 第三方脚本治理:能优化就优化,不能优化的想办法置后加载,避免阻塞主js。

代码中的优化

“%” 取余运算,“/” 取模运算

前端性能指标

  1. redirectStart:第一个HTTP重定向开始的时间戳。如果没有重定向,则此值为0。

  2. redirectEnd:最后一个HTTP重定向完成(即接收到最后一个重定向响应的最后一个字节)的时间戳。如果没有重定向,则此值为0。

  3. fetchStart:浏览器准备好使用HTTP请求来获取文档的时间戳。这个时间点会在检查任何应用缓存之前。

  4. domainLookupStart:域名查询开始的时间戳。如果使用了持续连接(keep-alive connection)或缓存,则此值为0。

  5. domainLookupEnd:域名查询完成的时间戳。

  6. connectStart:浏览器与服务器建立连接以获取文档的时间戳。如果文档是从缓存中获取的,则此值为0。

  7. secureConnectionStart:HTTPS握手开始的时间戳。如果页面是通过HTTP加载的,则此值为0。

  8. connectEnd:浏览器与服务器之间的连接建立完成的时间戳。如果文档是从缓存中获取的,则此值为0。

  9. requestStart:浏览器开始请求文档的时间戳。这个时间点会在发送HTTP请求的头部之后。

  10. responseStart:浏览器从服务器接收到第一个字节的时间戳。

  11. responseEnd:浏览器从服务器接收到最后一个字节(即响应完成)的时间戳。

  12. domLoading:开始解析文档的时间戳(并非所有浏览器都直接提供此属性,但它是解析过程的开始)。

  13. domInteractive:解析器完成了解析文档并且文档已经准备好与JavaScript交互的时间戳(即DOM完全加载和解析完成,但样式表、图片和子框架可能仍在加载)。

  14. domContentLoadedEventStart:DOMContentLoaded事件触发的时间戳,表示HTML文档解析完成并且所有脚本文件已下载完成。

  15. domContentLoadedEventEnd:DOMContentLoaded事件结束的时间戳,表示所有脚本文件已执行完成。

  16. domComplete:文档和所有子资源(例如图片、样式表等)都已完成加载的时间戳。

  17. loadEventStart:load事件开始的时间戳,表示所有资源(包括图片、样式表、脚本文件等)都已加载完成。

  18. loadEventEnd:load事件结束的时间戳,表示所有资源(包括图片、样式表、脚本文件等)都已执行完成。

  19. FCP(First Contentful Paint):首次内容绘制时间,即浏览器首次绘制任何文本、图像、非空白canvas或svg的时间

  20. SI(Speed Index):速度指数,即页面渲染速度的指标

  21. LCP(Largest Contentful Paint) :最大内容绘制时间,即页面中最大的可见内容元素绘制完成时间

  22. TBT(Total Blocking Time): 总阻塞时间,即页面主线程被阻塞的总时间

  23. TTI (Time to Interactive):TTI 是指页面变得可交互所需的时间

  24. TTD(Time to Display): TTD 是指页面显示所需的时间

FCP performance.getEntriesByType('navigation')[0].responseStart

SI let navigation = performance.getEntriesByType('navigation')[0].toJSON(); let si = navigation['loadEventEnd'] - navigation['domContentLoadedEventEnd']

LCP const [performanceData] = performance.getEntriesByType("navigation"); let lcp = performanceData.largestContentfulPaint;

TBT let navigationEntries = performance.getEntriesByType('navigation'); let navigationEntry = navigationEntries[0]; let navigationEntryJson = navigationEntry.toJSON(); let tbt = navigationEntryJson['totalBlockingTime'];

TTI const [performanceData] = performance.getEntriesByType("navigation"); if (navigationEntries.length > 0) { const interactiveTime = performanceData.domInteractive - performanceData.navigationStart; const loadTime = performanceData.loadEventEnd - performanceData.navigationStart; const tti = interactiveTime + loadTime; console.log('TTI:', tti); }

TTD

const [performanceData] = performance.getEntriesByType("navigation"); const ttd = performanceData.domInteractive - performanceData.requestStart;

白屏时间 // 计算白屏时间 (当前的) var whiteScreenTime = performanceData.domInteractive - performanceData.responseStart;

监控

接入监控流程
  1. 应用项目接入监控,上送数据
  2. 后端进行数据存储和分析
  3. 在数据监控平台上显示各个监控平台的报警信息(根据各数据的配置)
错误类型
  1. 页面错误,页面异常,导致页面白屏
  2. 网络错误,服务端异常导致的错误,或者不符合前后端约束的错误
错误收集
  1. try/catch 捕捉同步错误,不能捕获异步错误
  2. window.addEventListener('error') , window.onerror ,监听JS运行错误,和静态资源加载错误
  3. react 错误边界 componentDidCatch
  4. window.addEventListener('unhandledrejection') j监听异步报错
  5. 网页崩溃方案处理,通过service worker 发送心跳检测

数据收集

  1. 错误收集(JS运行错误,静态资源加载错误,应用崩溃错误,服务网络错误)
  2. 基础信息收集(设备信息收集,运行环境信息收集,用户信息收集)
  3. 用户行为信息收集(点击行为,关键链路)
  4. 性能监控 onload中,获取性能参数,PerformanceObserver