前端性能优化概述
- 降低时间消耗:让页面更加快速地展示在用户面前(如1秒内),或者尽快响应用户的操作(如50毫秒内)
- 减少资源占用:减少前端对本地缓存、网络带宽、CPU及内存的占用
降低时间损耗
在实际业务开发过程中,时间消耗较大的一般都在这三方面:网络请求、页面渲染和长任务。
网络请求优化
减少网络请求数量
合并文件:通过打包技术,将多个页面的js、css、组件等资源打包到一起,减少最终文件数量,比如原来项目中组件、js和样式文件加起来可能有上百个,最终打包出去的可能也就10个左右,大大减少了网络请求数量。
雪碧图:特别适合将多个小图片合并成一张大图。即使再小的一张图片,在请求时也要建立一次网络链接,走一遍请求过程,请求1张10B大小的图片和请求一张1KB大小的图片时间差别并不大,如果把100张10B的图片合成为1张1KB的图片,那么整体加载速度可能提升了100倍,使用雪碧图的关键是如何自动化合成图片并生成样式。
字体图标:使用字体图标代替传统的图片图标,字体图标可以通过CSS直接渲染,无需额外的网络请求。
懒加载:针对图片等资源使用懒加载技术,只在出现在视窗区域中时才加载资源。
浏览器缓存:通过缓存技术,特别是强制缓存,减少请求资源数量,js、css、图片等资源尽量使用强制缓存。
减少网络请求大小
打包压缩:对生产环境的代码进行压缩,如使用UglifyJS、Terser、Webpack插件(TerserWebpackPlugin、MiniCssExtractPlugin)等
代码拆分:如果把所有的文件打包成一个文件,那么必然导致这个文件过大,影响首次加载的速度,可以按照使用频次进行拆包,只把经常使用的包打到公共包中,如使用webpack的splitChunks功能来完成该功能。
按需引入:一些第三方库或者组件库都支持按需引入,减少无用的代码
异步路由:通过异步路由,将页面的代码从打包文件中分离出来,减少主文件的大小
gzip压缩:通过nginx等服务将返回的数据进行gzip压缩,gzip可以有效提升网络传输速度,但是要注意平衡压缩质量,压缩的太狠,虽然传输时间减少,但是服务器压缩时间就会加长,可通过测试找出最合理的配置
图片压缩:不要直接使用UI设计师提供的图片,记得先压缩一下,如TinyPNG
更换图片格式:不知道你注意到没有,不同格式的图片大小是不一样的,JPEG的通常要比PNG的小,而WebP的图片压缩体积大约只有JPEG的2/3。
加快网络速度
http2代替http:HTTP/1.1中,一个连接在同一时间只能处理一个请求,而HTTP/2通过多路复用技术解决了这个问题,允许单个TCP连接上并发多个请求,而且HTTP/2使用HPACK算法对头部信息进行压缩,大大减少了传输的数据量。
提升服务器带宽:没啥说的,如果经费充足,提高服务器带宽能同时为更多用户提供良好的传输速度。
使用CDN:服务器带宽一般有限,那么可以将静态资源放到CDN服务上,由专业的供应商提供更好的网络传输速度。
- 并行加载
不同资源采用不同域名:不知道你发现没有,浏览器请求资源时,同一个域名下最多只能同时发起6-8个请求,即使你一下子发出几十个文件请求,浏览器也只会按照最大并发6-8个来顺序执行,那如果我们给不同的资源分配不同的域名呢,是不是就可以同时请求更多的资源以加快整体完成速度了。
并发请求:业务开发中,对于多个没有先后顺序的请求,可以通过Promise.all并行发起请求,而不必await上一个完成再发送下一个。
- 预加载
DNS预解析:如果打开淘宝网站,就能在head中发现如下的代码,通过浏览器闲时DNS预解析,可以加快后续资源加载速度
资源预加载:可以在页面或者某个功能呈现之前提前加载其所需的资源,比如在浏览器闲时加载后续可能用到的资源,或者为了后续效果更流畅先把所有资源加载完成,就像之前很流行的H5动画,上来会有个加载进度的动画,在这期间就是在预加载后续所用到的图片或音频资源,如果不这么做,动画中用到某个图片时临时加载很可能会有个短暂的白屏加载过程,这样动画效果就给人感觉很不流畅。
页面渲染优化
网络请求获取完资源后,必然要把画面渲染出来,这个过程中可能会产生一些耗时严重的问题,页面渲染的优化可以考虑从这几个方面进行:首屏优化、减少不必要的渲染次数、GPU加速和Canvas渲染优化等。
- 首屏优化
- 通过服务端渲染技术(SSR)加快首屏的渲染速度
- 内容懒加载
- 异步加载js
- 减少渲染次数
- 批量操作DOM
- 避免使用innerHTML进行大量更新
- 优化数据绑定
- 使用虚拟列表技术
- 避免频繁的样式更改
- 使用虚拟DOM
- 防抖或节流
- 减少重排
- Canvas优化
- 控制Canvas尺寸
- 图层管理与分层渲染
- 使用离屏Canvas
- 减少上下文切换
- 动静分离
- 长任务
将耗时较长的同步任务拆成多个异步任务:根据浏览器事件循环机制可以知道,长的同步任务会阻塞页面渲染,而拆成小任务并通过异步(如setTimeout)将任务依次执行,则不会阻塞页面渲染
使用Worker执行长任务:Web Workers允许你在浏览器的后台线程中运行JavaScript,这样就不会阻塞主线程
更合理的数据结构:有时长任务可能是因为数据结构不合理导致查询、变更耗时较长,可以考虑修改数据结构减低时间复杂度
减少资源占用
空间占用: 前端缓存会占据一定的用户计算机空间,比如我们经常看到某某APP占据了好多个G的手机空间,所以还是尽量减少缓存占用,及时清理缓存。比如能用sessionStorage的地方就不要用localStorage,因为sessionStorage会在页面关闭后自动清除缓存,而localStorage则需要代码执行清理逻辑。
内存占用: 对于大部分web应用来说,用户一般用完就走,所以内存占用情况并不突出,而如果是那种长时间放置的监控画面,如果内存处理不好,则很容易随着时间的增长而导致内存溢出,建议平时也养成良好的编码习惯,比如组件销毁时记得清除组件中使用的对象(eCharts等实例)、及时清理定时器、清空闭包中使用的变量等。
网络占用: 有些场景下需要循环进行网络请求,比如需要每隔一段时间刷新页面中某个数据的状态,很多同学会不假思索地使用setInterval进行轮询。
总结
在产品交互同质化严重的时代,性能变的越来越重要,要更加重视性能优化,把性能当做功能来对待。
产品性能优化主要包括降低时间消耗和减少资源占用两个方面,降低时间消耗可以从网络请求优化、页面渲染优化和长任务优化三个角度进行。
对于不好进行前端优化的功能,可以考虑通过优化产品交互或转移到后端的方式来转移复杂度,同时通过友好的提示,让用户知道当前功能的处理状态。
在进行性能优化时需要搞懂流程,找到优化的关键路径,同时善用工具,快速定位问题,并重视前端性能的监控,主动发现问题解决问题。
