背景知识
随着业务的不断迭代,项目日渐壮大,为了给用户提供更优的体验,性能优化是前端开发避不开的话题。最近在学习整理前端性能优化方面的知识,看了一些文章,感觉文章多了比较零散,这里做个总结,可能有一些不全面的地方,欢迎大家进行补充~🎉
- 来自Google的数据表明,一个有10条数据0.4秒能加载完的页面,变成30条数据0.9秒加载完之后,流量和广告收入下降90%。
- Google Map 首页文件大小从100KB减小到70-80KB后,流量在第一周涨了10%,接下来的三周涨了25%。
- 亚马逊的数据表明:加载时间增加100毫秒,销量就下降1%。
性能优化的本质:在用户输入url到站点完整把整个页面展示出来的过程中,通过各种优化策略和方法,让页面加载更快;在用户使用过程中,让用户的操作响应更及时,有更好的用户体验。主要是从浏览器的加载和渲染两方面入手。
雅虎性能优化军规
雅虎军规是雅虎的开发人员在总结了网站的不合理部分后,提出的优化网站性能提高的一套方法规则,非常适合初学者,大家可以参考一下。
优化指标
这里只是抽出了一部分进行分享,更多更详细的见:https://segmentfault.com/a/1190000041753539
以用户为中心
- First Paint 首次绘制(FP)
这个指标用于记录页面第一次绘制像素的时间,如显示页面背景色。FP不包含默认背景绘制,但包含非默认的背景绘制。 - First contentful paint 首次内容绘制 (FCP)
LCP是指页面开始加载到最大文本块内容或图片显示在页面中的时间。如果 FP 及 FCP 两指标在 2 秒内完成的话我们的页面就算体验优秀。 - Largest contentful paint 最大内容绘制 (LCP)
用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。官方推荐的时间区间,在 2.5 秒内表示体验优秀 - First input delay 首次输入延迟 (FID)
首次输入延迟,FID(First Input Delay),记录在 FCP 和 TTI 之间用户首次与页面交互时响应的延迟。 - Time to Interactive 可交互时间 (TTI)
首次可交互时间,TTI(Time to Interactive)。这个指标计算过程略微复杂,它需要满足以下几个条件:- 从 FCP 指标后开始计算
- 持续 5 秒内无长任务(执行时间超过 50 ms)且无两个以上正在进行中的 GET 请求
- 往前回溯至 5 秒前的最后一个长任务结束的时间
对于用户交互(比如点击事件),推荐的响应时间是 100ms 以内。那么为了达成这个目标,推荐在空闲时间里执行任务不超过 50ms( W3C 也有这样的标准规定),这样能在用户无感知的情况下响应用户的交互,否则就会造成延迟感。
- Total blocking time 总阻塞时间 (TBT)
阻塞总时间,TBT(Total Blocking Time),记录在 FCP 到 TTI 之间所有长任务的阻塞时间总和。 - Cumulative layout shift 累积布局偏移 (CLS)
累计位移偏移,CLS(Cumulative Layout Shift),记录了页面上非预期的位移波动。页面渲染过程中突然插入一张巨大的图片或者说点击了某个按钮突然动态插入了一块内容等等相当影响用户体验的网站。这个指标就是为这种情况而生的,计算方式为:位移影响的面积 * 位移距离。
三大核心工具
Google 在2020年五月提出了网站用户体验的三大核心指标:
Largest Contentful Paint (LCP)
LCP代表了页面的速度指标,虽然还存在其他的一些体现速度的指标,但LCP能体现的东西更多一些。一是指标实时更新,数据更精确,二是代表着页面最大元素的渲染时间,通常来说页面中最大元素的快速载入能让用户感觉性能还挺好。
最大元素:
- <img> 标签
- <image> 在svg中的image标签
- <video> video标签
- CSS background url()加载的图片
- 包含内联或文本的块级元素
测量工具
-
线上测量工具
- Chrome User Experience Report
- PageSpeed Insights
- Search Console (Core Web Vitals report)
- web-vitals JavaScript library
-
实验室测量工具
- Chrome DevTools
- Lighthouse
- WebPageTest
-
原生JS API测量
使用PerformanceObserver接口,目前除了IE不支持,其他浏览器基本都支持了。new PerformanceObserver((entryList) => {for (const entry of entryList.getEntries()) {console.log('LCP candidate:', entry.startTime, entry);} }).observe({type: 'largest-contentful-paint', buffered: true});
影响因素
- 服务端响应时间
- Javascript和CSS引起的渲染卡顿
- 资源加载时间
- 客户端渲染
First Input Delay (FID)
FID代表了页面的交互体验指标,就是看用户交互事件触发到页面响应中间耗时多少,如果其中有长任务发生的话那么势必会造成响应时间变长,推荐响应用户交互在 100ms 以内。
测量工具
-
线上测量工具
- Chrome User Experience Report
- PageSpeed Insights
- Search Console (Core Web Vitals report)
- web-vitals JavaScript library
-
原生JS API测量
new PerformanceObserver((entryList) => {for (const entry of entryList.getEntries()) {const delay = entry.processingStart - entry.startTime;console.log('FID candidate:', delay, entry);} }).observe({type: 'first-input', buffered: true});
影响因素
- 减少第三方代码的影响
- 减少Javascript的执行时间
- 最小化主线程工作
- 减小请求数量和请求文件大小
Cumulative Layout Shift (CLS)
CLS代表了页面的稳定指标,它能衡量页面是否排版稳定。尤其在手机上这个指标更为重要,因为手机屏幕挺小,CLS值一大的话会让用户觉得页面体验做的很差。CLS的分数在0.1或以下,则为Good。
浏览器会监控两桢之间发生移动的不稳定元素。布局移动分数由2个元素决定:impact fraction
和distance fraction
。
layout shift score = impact fraction * distance fraction
下面例子中,竖向距离更大,该元素相对适口移动了25%的距离,所以distance fraction
是0.25。所以布局移动分数是 0.75 * 0.25 = 0.1875
。
但是要注意的是,并不是所有的布局移动都是不好的,很多web网站都会改变元素的开始位置。只有当布局移动是非用户预期的,才是不好的。
换句话说,当用户点击了按钮,布局进行了改动,这是ok的,CLS的JS API中有一个字段hadRecentInput,用来标识500ms内是否有用户数据,视情况而定,可以忽略这个计算。
测量工具
- 线上测量工具
- Chrome User Experience Report
- PageSpeed Insights
- Search Console (Core Web Vitals report)
- web-vitals JavaScript library
- 实验室测量工具
- Chrome DevTools
- Lighthouse
- WebPageTest
- 原生JS API测量
let cls = 0; new PerformanceObserver((entryList) => {for (const entry of entryList.getEntries()) {if (!entry.hadRecentInput) {cls += entry.value;console.log(‘Current CLS value:’, cls, entry);}} }).observe({type: ‘layout-shift’, buffered: true});
影响因素
通过下面的原则避免非预期布局移动:
- 图片或视屏元素有大小属性,或者给他们保留一个空间大小,设置width、height,或者使用 unsized-media feature policy 。
- 不要在一个已存在的元素上面插入内容,除了相应用户输入。
- 使用animation或transition而不是直接触发布局改变。
性能工具
调试工具和Web API的使用参考:https://juejin.cn/post/6911472693405548557#heading-21
工具列表:
- Lighthouse
- PageSpeed Insights
- Chrome DevTools
- Search Console (Core Web Vitals report)
- web.dev’s提供的测量工具
- web-vitals 扩展
- Chrome User Experience Report
我们已经知道了有哪些工具,那么这些工具怎么使用呢?
- 首先我们可以使用Lighthouse,在本地进行测量,根据报告给出的一些建议进行优化;
- 发布之后,我们可以使用PageSpeed Insights去看下线上的性能情况;
- 接着,我们可以使用Chrome User Experience Report API去捞取线上过去28天的数据;
- 发现数据有异常,我们可以使用DevTools工具进行具体代码定位分析;
- 使用Search Console’s Core Web Vitals report查看网站功能整体情况;
- 使用Web Vitals扩展方便的看页面核心指标情况;
优化实践方案
对于前端应用来说,网络耗时、页面加载耗时、脚本执行耗时、渲染耗时等耗时情况会影响用户的等待时长,而 CPU
占用、内存占用、本地缓存占用等则可能会导致页面卡顿甚至卡死。
因此,性能优化可以分别从耗时和资源占用两方面来解决,也可以理解成时间和空间两个方面。
更加详细的内容可见:https://godbasin.github.io/2022/03/06/front-end-performance-optimization/
时间角度
在时间角度进行优化主要是减少耗时,浏览器在页面加载的过程中,主要会进行以下的步骤:
- 网络请求相关(发起 HTTP 请求从服务端获取页面资源,包括 HTML/CSS/JS/图片资源等)
- 浏览器解析 HTML 和渲染页面
- 加载 Javascript 代码时会暂停页面渲染(包括解析到外部资源,会发起 HTTP 请求获取并加载)
耗时优化的着手点:
- 网络请求优化
- 首屏加载优化
- 渲染过程优化
- 计算/逻辑运行提速
网络请求优化
目标在于减少网络资源的请求和加载耗时,如果考虑 HTTP 请求过程,显然我们可以从几个角度来进行优化:
- 请求链路:DNS 查询、部署 CDN 节点、缓存等
- 数据大小:代码大小、图片资源等
对于请求链路,核心的方案常常包括使用缓存,比如 DNS 缓存、CDN 缓存、HTTP 缓存、后台缓存等等,前端的话还可以考虑使用
Service Worker、PWA
等技术。使用缓存并非万能药,很多使用由于缓存的存在,我们在功能更新修复的时候还需要考虑缓存的情况。除此之外,还可以考虑使用
HTTP/2、HTTP/3 等提升资源请求速度,以及对多个请求进行合并,减少通信次数;对请求进行域名拆分,提升并发请求数量。
数据大小则主要考对请求资源进行合理的拆分(CSS、Javascript 脚本、图片/音频/视频等)和压缩,减少请求资源的体积,比如使用
Tree-shaking、代码分割、移除用不上的依赖项等。
在请求资源返回后,浏览器会进行解析和加载,这个过程会影响页面的可见时间,通过对首屏加载的优化,可有效地提升用户体验。
首屏加载优化
首屏加载优化核心点在于两部分:
- 将页面内容尽快地展示给用户,减少页面白屏时间。
- 将用户可操作的时间尽量提前,避免用户无法操作的卡顿体验。
整体的优化思路包括:尽可能提前页面可见,以及将用户可交互的时间提前。一般来说,我们需要尽可能地降低首屏需要的代码量和执行耗时,可以通过以下方式进行:
- 对页面的内容进行分片/分屏加载
- 仅加载需要的资源,通过异步或是懒加载的方式加载剩余资源
- 使用骨架屏进行预渲染
- 使用差异化服务,比如读写分离,对于不同场景按需加载所需要的模块
- 使用服务端直出渲染,减少页面二次请求和渲染的耗时
有些时候,我们的页面也需要在客户端进行展示,此时可充分利用客户端的优势:
- 配合客户端进行资源预请求和预加载,比如使用预热 Web 容器
- 配合客户端将资源和数据进行离线,可用于下一次页面的快速渲染
- 使用秒看技术,通过生成预览图片的方式提前将页面内容提供给用户
除了首屏渲染以外,用户在浏览器页面过程中,也会触发页面的二次运算和渲染,此时需要进行渲染过程的优化。
渲染过程优化
渲染过程的优化可以理解成首屏加载完成后,用户的操作交互触发的二次渲染。
主要思路是减少用户的操作等待时间,以及通过将页面渲染帧率保持在 60FPS 左右,提升页面交互和渲染的流畅度。包括但不限于以下方案:
- 使用资源预加载,提升空闲时间的资源利用率
- 减少/合并 DOM 操作,减少浏览器渲染过程中的计算耗时
- 使用离屏渲染,在页面不可见的地方提前进行渲染(比如 Canvas 离屏渲染)
- 通过合理使用浏览器 GPU 能力,提升浏览器渲染效率(比如使用 css transform 代替 Canvas 缩放绘制)
以上这些,是对常见的 Web 页面渲染优化方案。对于运算逻辑复杂、计算量较大的业务逻辑,我们还需要进行计算/逻辑运行的提速。
计算/逻辑运行提速
计算/逻辑运行速度优化的主要思路是“拆大为小、多路并行”,方式包括但不限于:
- 通过将 Javscript 大任务进行拆解,结合异步任务的管理,避免出现长时间计算导致页面卡顿的情况
- 将耗时长且非关键逻辑的计算拆离,比如使用 Web Worker
- 通过使用运行效率更高的方式,减少计算耗时,比如使用 Webassembly
- 通过将计算过程提前,减少计算等待时长,比如使用 AOT 技术
- 通过使用更优的算法或是存储结构,提升计算效率,比如 VSCode 使用红黑树优化文本缓冲区的计算
- 通过将计算结果缓存的方式,减少运算次数
空间角度
在做性能优化的时候,其实很多情况下会依赖时间换空间、空间换时间等方式,只能根据自己项目的实际情况做出取舍,选择相对合适的一种方案去进行优化。
资源占用常见的优化方式包括:
- 合理使用缓存,不滥用用户的缓存资源(比如浏览器缓存、IndexDB),及时进行缓存清理
- 避免存在内存泄露,比如尽量避免全局变量的使用、及时解除引用等
- 避免复杂/异常的递归调用,导致调用栈的溢出
- 通过使用数据结构享元的方式,减少对象的创建,从而减少内存占用
参考链接
文档:https://juejin.cn/post/6911472693405548557
ppt演示:https://link.juejin.cn/?target=https%3A%2F%2Falexwjj.github.io%2Ffe-optimize-ppt%2F
https://www.163.com/dy/article/GATVPHFL05528G99.html