前端性能优化不管是在面试中还是在实际开发过程中,都是每一个前端开发工程师都必不可少的能力。本文总结本人多年开发经验中对前端性能优化的理解,希望对大家有所帮助,因涉及的优化方向较多,针对某些细节不再详细说明,大家有兴趣的可深入了解,话不多说,正文开始。
重要性
一个好的前端项目性能非常重要,特别是面向ToC的用户,好的用户体验可以极大的提高业务转化率,从而性能的好坏关乎到业务的营收。对于一个商业性公司只要是和钱相关,都是极其敏感且重要的。
度量方式
2020年 Google 提出了新一代 Web 性能体验指标 Core Web Vitals,其中包括了 LCP、FID、CLS 三大指标。
- Largest Contentful Paint (LCP): 衡量加载体验:为了提供良好的用户体验, LCP 应该在页面首次开始加载后的 2.5 秒内发生。
- First Input Delay (FID): 衡量可交互性,为了提供良好的用户体验,页面的 FID 应当小于 100毫秒。
- Cumulative Layout Shift (CLS):衡量视觉稳定性,为了提供良好的用户体验,页面的CLS应保持小于 0.1。
针对这些指标直接可以通过浏览器开发工具中的Lighthouse得出是否达到标准。通过这样的方式得出的结论快速直观,对原本网站无侵入,不影响真实用户的性能。但是也有缺点,不支持复杂的业务逻辑场景,监测的数据量太小,不能还原大部分真实用户的使用情况。
所以为了得到真实且全面的数据,大部分的公司都会开发一套监测方案,或者使用第三方监测平台,这样会对网站有一定的性能影响,但是可通过更全面的性能数据分析可优化的方向。
性能优化方向
基于上面的三个体验指标,我们可以从页面加载的生命周期进行优化,页面加载前的预处理,加载过程中,页面渲染时,用户界面交互等几个阶段,下面将针对不同的阶段进行优化,大家可根据自己项目的情况针对性的选择优化。
加载前的预处理
使用 dns-prefetch、preconnect 减少DNS解析,建立TCP连接以及执行TLS握手时间,dns-prefetch: 告知浏览器对指定域名进行DNS解析。当后续请求该域名资源时可省掉DNS解析的时间。preconnect: 告知浏览器与指定域名的服务器建立连接。当后续请求该域名资源时,可直接使用已建立好的连接,省掉了 DNS+TCP+TLS 的时间
<link rel="dns-prefetch" href="https://s1.static.com">
<link rel="preconnect" href="https://s1.static.com">
使用 preload/prefetch 让浏览器提前加载需要的资源,preload可以指明哪些资源是在页面加载完成后即刻需要的,浏览器在主渲染机制介入前就进行预加载,这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能;prefetch其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。切记不要将 preload 和 prefetch 进行混用,它们适用于不同的场景,如对同一个资源同时使用 preload 和 prefetch 会造成不必要的二次下载。
<link href="xx.js" rel="prefetch">
<!--as表示指定资源类型-->
<link href="xx.js" rel="preload" as="script">
加载过程中
1. 尽可能的减小资源的大小
- 业务代码本身尽可能的不要重复,提高组件化的使用,提示代码的复用率,这里不止是JS,CSS样式也是一样
- 压缩静态资源,一般脚手架都默认会处理,自建项目可检查是否有压缩
- html中的DOM层级控制不要太深以及减少不必要的DOM使用,尽可能发挥伪元素及CSS的使用
- 检查项目的依赖包是否有重复引用的情况,不同的依赖包可能引用了同一个不同版本的包,可通过webpack-bundle-analyzer插件分析查看
- UI组件库或其他库使用babel-plugin-import插件进行按需加载
- 组件按需加载,使用AsyncComponent仅加载首屏组件
- 动态导入第三方比较大的模块,import('/modules/echart.js) .then((module) => {}),但不要滥用,结合实际场景使用
- 减小第三方库的大小,如Moment.js/lodash等,使用轻量级别替代方案或者自己重新实现
- 对首评秒开要求较高的,可对首屏请求的接口进行拆分,快速响应首屏需要用到的字段,其他的数据异步加载
- 使用tree shaking,当我们在项目中引入其他模块时,他会自动将我们用不到的代码,或者永远不会执行的代码摇掉,在Uglify阶段查出,不打包到bundle中
- HTTP头部Cookie的精简,去除不必要的Cookie,静态资源做独立域名部署,避免请求携带Cookie
- HTTP头部开启gzip压缩,可大大减小网络传输的数据量
- HTTP头部开启keep-alive
- 升级HTTP到2.0,2.0的头部压缩,减少了数据传输量,能够节省消息头占用的网络的流量,且还有多路复用等优势
2. 尽可能的减少资源的次数
- 部分小体量级别的JS/CSS可内联到HTML中,减少请求数量
- 减小预检请求OPTIONS的发起,可通过服务端设置Access-Control-Max-Age字段或改为发起简单请求
- 取消无效请求,表单提交频繁点击,路由切换时还有未完成的请求。这些都会产生无效请求,对服务器和用户体验都是不好的
- 缓存策略
开启http强缓存与协商缓存,对于不同类型的资源使用不同的缓存策略
静态资源开启CDN服务
对于不常变化的数据包括外部JS/CSS资源,可进行前端浏览器缓存,减少请求,但此类缓存需设定好清除及更新的机制
3. 其他资源优化
- 图片webp使用,对于支持的设备使用webp
- 图片裁剪,针对使用场景进行相应的裁剪
- 大图不要打包在项目中,上传到单独的静态资源服务器或是CDN中
- 图片上传前进行压缩,切记不要使用原图
- 设置图片标签尺寸大小,防止图片加载中导致页面布局抖动,影响CLS指标的数值
- 超出屏幕外的图片开启懒加载
- 对于项目中大量的小图标可使用iconfont字体方案
- 使用第三方字体库时尽可能按需文字生成
- 加载字体的时候会导致页面文字有一定的闪烁抖动,可在进入需要用到的页面前使用preload提前进行加载
页面渲染时
开启骨架屏,提升用户体验,避免加载到渲染过程中都是白屏阶段
对于大量列表的滚到使用虚拟列表
尽量多使用CSS3动画
使用 requestAnimationFrame 监听帧变化,使得在正确的时间进行渲染
合理使用CSS,避免通配符,最大化样式继承,少用标签选择器,减少过深嵌套等
用户界面交互
- 减少页面重排、重绘
- 防抖节流的使用
- 合理使用 requestAnimationFrame 动画代替 setTimeout
- 开启GPU加速,CSS中可使用以下属性(CSS3 transitions、CSS3 3D transforms、Opacity、Canvas、webGL、Video)来触发 GPU 渲染
- 减少 JavaScript 脚本执行时间,把一些和 DOM 操作无关且耗时的任务放到 Web Workers 中去执行
- 对未来某个时间内需要执行动画的元素,将其标记为 will-change,这样渲染引擎会将该元素单独生成一个图层
最后
本文对前端性能优化的方向列举了不少,除此之外也还有很多是没有涉及到的,比如小程序内,Vue/React框架中特殊的其他优化,配合App原生能力优化等。以上优化方向的说明就较为简洁,具体的实操及原理有兴趣的同学可以多研究,面对这么多的方向优化,究竟如何选择呢。
没有所谓的绝对优化,都需要结合当前项目的应用场景及对项目全量的性能分析,找到某个方向的不足,针对性的优化,选择合适的方案。希望大家都能找到自己合适的优化方向,把项目优化的妥妥的。