你想要的性能优化方案都在这里了
小麦2024年09月29日2760 字
用户体验,毫无疑问是前端工程师的关注重点,我们之前介绍的计算机网络,编程语言,工具链,用户界面和应用框架,
其实都是为用户体验而服务的,这里说的用户体验是广义上的,包含终端用户体验(UX)和开发者体验(DX)。
前端作为人机交互的桥梁,在现代工具和框架的加持下,又该如何把人机交互体验做到极致呢?
我们将从四个方面讨论大家默认的,狭义上的终端用户体验,分别是:性能、UI 设计、可访问性和个性化。
由于内容较多,本期我们先聚焦于性能优化这个主题。
在进行性能优化前,我们应该通过性能分析工具,来观察页面各阶段的耗时和性能指标,有针对性的进行优化,避免过早或过度优化。
以最常用的 Chrome DevTools 的 Performance 面板为例,值得关注的地方是:
Network 栏中标红的资源(Render-blocking resources),这些资源会阻塞页面渲染。
Timings 栏中的各时间戳,FCP 表示用户看到页面第一个元素的时刻,LCP 差不多是页面完整呈现的时间。
Main 栏火焰图中标红的函数调用,表示执行耗时较长的函数(Long Task),是我们排查低性能代码的重要途径。
前端的性能问题通常可以归结为三个方面:页面加载时间太长,交互过程不流畅和资源消耗过度。
导致页面加载时间太长的原因有很多,
比如资源体积过大,网络延迟较高,缺少缓存,渲染受阻等等。
针对资源体积过大问题,有两个主要途径,减小产物大小,减少传输量。
现代前端框架集成的构建工具,提供了压缩(Compression),树摇(Tree Shaking),依赖外置(Externals)功能来减小产物大小。
同时提供代码拆分(Code Splitting),按需加载(Lazy Loading)等基本功能来减少传输量。
主流前端应用框架还支持服务端渲染(Server Side Rendering,SSR),
部分基于 React 的应用框架,还提供更先进的服务器组件(React Server Component,RSC),以及流式渲染技术(Streaming Rendering)。
和服务端渲染不同的是,流式渲染技术实现了分段渲染和传输,可以让用户尽可能快地看见有效内容。
此外还有静态生成(Static Generation)技术,它在构建阶段就生成好静态页面,用户直接访问静态页面,省掉了大量的客户端脚本。
它包含两种形态:静态站点生成(Static Site Generation,SSG)和增量静态生成(Incremental Site Generation,ISG),适用于不同的业务场景。
说完资源体积过大的问题,
我们再来看看网络延迟高怎么解决。
我们可以启用 HTTP/2 协议提升网络并发传输效率,
使用 CDN 分发静态资源,
在边缘计算节点部署无服务器(Serverless)应用。
使用浏览器资源提示属性(Browser Resource Hints),如预加载(preload),预取(prefetch),DNS 预解析(dns-prefetch),预连接(preconnect)。
对图片进行专项优化,如使用现代图片格式 webp 和 avif,预渲染缩略图,在标准 Img 的 loading、fetchpriority、decoding、srcset 属性上进行优化等等。
以上就是网络延迟高的通用解法。
接下来我们再来看看缓存的必要性。
前端应用在许多环节都会用到缓存,大致可以分为本地缓存和远端缓存两类。
顾名思义,本地缓存是指存储在终端用户设备上的缓存,而远端缓存则是存储在服务设备上的缓存。
我们可以利用 HTTP 协议的缓存头,如 Cache-Control、Last-Modified、ETag 等来协商缓存的存储方式。
也可以用浏览器存储 API,如 LocalStorage、IndexedDB 来手动管理缓存。
或者利用 Service Worker 天生的缓存功能来最大程度缩短页面加载时间。
我们也可以在服务端或代理设备上配置远端缓存,如内存缓存,边缘缓存,网关缓存等方案。
提高缓存利用率能有效提升应用的响应速度,和整体服务的健壮性。
如果你已经做完资源体积优化,网络延迟优化,缓存优化,还想做地更极致,那么页面的渲染过程就是一个不错的优化方向。
为了进一步缩短由于渲染过程带来的白屏窗口,
我们可以将非关键的 JS 文件标记为异步加载(async 或 defer),来避免他们阻塞 HTML 解析。
另外,也可以采用 Web Worker 和 WebAssembly 技术,将非常消耗计算资源的任务移出主线程和加速计算。
对于白屏时间较长的 SPA 单页应用,可以在主文档全局变量中预写入数据,来减少首屏代码中的接口请求,加速页面渲染。
小结一下,资源体积过大,网络延迟较高,缺少缓存,渲染过程有阻碍,都会导致页面加载时间太长。
聊完页面加载时间太长这个问题,
接下来我们再看交互过程不流畅如何解决。
导致交互过程不流畅的原因主要有三类:海量的数据,大量的动画以及频繁的交互。
海量数据渲染场景包括长列表展示和大数据可视化。
对于长列表展示:我们可以通过分页(Pagination)和虚拟滚动(Virtual Scrolling)技术,选择性渲染在用户可见范围内的元素,而不是整个列表。
比如 Ant Design 组件库的 Select 组件就使用了虚拟滚动技术。
对于大数据可视化:我们可以借助数据抽样,分片渲染,Canvas 渲染,WebGL 加速技术来分摊图形计算压力。
许多开源图表库和 2D、3D 渲染引擎都支持 Canvas 和 WebGL。
此外,还可以通过离屏渲染(Offscreen Rendering)技术在屏幕外的缓冲区中进行图像合成,再一次性显示,减少屏幕卡顿和闪烁。
接着,大量的动画需求,也是容易引起交互过程不流畅的重要因素。
许多页面会包含动效,特别是大促、产品营销场景。如果全部使用未经优化的 animation 或 JS 动画,会显著影响页面的帧率(FPS)。
首先最基本的,应该多在动画上使用带有硬件加速功能的 CSS 属性,如 transform,opacity,perspective 等。
也可以使用浏览器提供的 requestAnimationFrame 函数。
或是开源动效方案,如 Lottie 和 Framer Motion 等,它们都可以提供不错的性能和效果。
许多用户在界面反馈不及时的情况下,会更频繁地点击、输入或滑动,严重时可能导致程序异常。
除了大家都知道的防抖(Debounce)和节流(Throttle)外,
我们还可以利用 UI 框架提供的方案,比如:Vue 提供的 v-show 和 keep-alive 组件,React 提供的 useTransition、useDeferredValue 和即将推出的 useOptimistic 来实现更友好的交互反馈。
最后是导致资源消耗过度的因素,
主要有两类:不合理的资源加载、代码质量问题。
这里的资源包括计算资源(CPU、内存占用),网络资源(带宽、流量),以及依赖电池供电设备的电量等等。
错误的构建设置,未经优化的静态资源,三方库滥用,无效的缓存等,都会导致不合理的资源加载。
要解决这些问题,首先需要选择主流的应用框架,
其次是克制地使用三方依赖,
然后是使用分析工具了解构建产物的组成,去除或拆分过大的依赖包等等。
接下来是常见的代码质量问题,
比如:只监听不卸载的事件处理器,长时间执行的同步代码,频繁的网络请求等都是典型的代码质量问题。代码问题是一直存在的,只要是人就会犯错。
要减少代码错误,除了吸取前人的经验教训外,
我想更多需要借助自动化工具,
甚至是 AI 助手来分析和修复可能产生问题的代码。
前面我们提到的一些优化方案,需要依赖浏览器特性才能实现,
比如:浏览器资源提示,图片优化属性,Web Storage API,Web Worker 等。
其实目前许多超级 App 内的 H5、小程序都不是直接运行在浏览器中,而是运行在所谓的容器(Container)中,可以理解为一个经过 App 定制和增强的浏览器内核。
基于容器,我们可以实现更多浏览器实现不了的强大优化方案。比如:
离线包:在特定的时机下载静态资源,容器从离线包中加载依赖,可以提升 H5 应用的加载速度。
小程序:一套完全独立于传统 PWA 的架构,从渲染引擎开始重构,可以拥有更接近原生渲染的性能体验。
本期我们花了较大的篇幅,介绍了前端性能问题的产生原因以及通用应对策略,不过这仅仅是用户体验的一部分。
下一期我们会从 UI 优化,可访问性和个性化这三个角度出发,再聊用户体验。
记得点个关注,我是小麦,我们下期再见。