
关于(前端)性能优化的个人简短梳理总结
- Published On
对于这个问题的回答思路,如果(预设)问题本身问得宽泛,那么回答时(自我梳理时)可以切入的角度,笔者联想到了这么两个:
- 时间、空间
- 预先优化、事后优化
时间、空间
比如从性能优化的方向维度,我个人将其归类为两个大的维度:时间、和空间,空间上好理解,比如客户端加载的各种资源文件本身的体积,你可以预先的有意去对这方面作处理;而时间维度上,虽然我们说比如前面的例子,空间上资源体积减小,那么时间上加载的时间也变短,但是如果只从时间上的优化上讲,更直观的例子比如:基于浏览器缓存,妥善设置部分资源的响应头(比如缓存时间、缓存失效条件),在用户重复请求后走缓存,这便是时间维度上的优化例子。
预先、事后
又比如从优化行为本身来讲、来分类,(我之前刷到网络上一个面试官分享他的面试点,比如当他提问求职者关于性能优化的问题,
我之前面试别人的时候都会问类似“你平时是如何做性能优化”或者“出现性能问题你一般是怎么解决的”。有经验的人==基本都会从浏览器表现开始==,对问题进行归类,然后列举几种常见的性能问题如何进行排查、定因,都有哪些趁手的工具,而最后如何解决的反而不那么重要。而新手基本都是起手 React.memo、useCallback useMemo 就结束了。
如果面试者上来就是一大堆的 React.useMemo / useCallback api 列举,那么可能成为减分项,毕竟这可能在部分面试官看来是缺少实操的偏八股文回答。他也给出了作为对比的正面例子:从具体场景、具体处理手段出发,体现出自己的实际经验。
当然的,笔者从求职者角度,觉得两者都有其合理点,这里也将其分为预先的性能优化、和事后优化,对于前者,比如前面的八股文例子,你了解了使用 React useCallback 缓存函数指向、useMemo 缓存高开销计算值、memo 规避子组件的 re-render , 那么当面试者有意的提出这些要点,代表其有能力有意的从这些方面做预先的性能优化(虽然 React 官方文档并不推荐一味地使用 useMemo 这类缓存 hook —— 同时随着 React 版本迭代,后续可能不再需要开发者操心这一类缓存指向问题,更进一步的,摆脱这些 hook 的使用(当前就有着 React 官方最新提出的 React Compiler),当然的,可能确实会给人缺乏实际经验的观感。
那么,就从具体场景出发,从实际的处理方法出发,以笔者的实际开发经验:往往遇到页面的一些比如长时间未响应问题、或者卡顿问题,会优先的打开浏览器的控制台面板,切到 network 网络面板,看看是否一些资源请求长时间处于 pending 状态,切到基于 框架或库的开发者工具,比如 React devtools / Redux devtools ,比如打开这种开发者工具中的“重渲染高亮”开关,直观地去观察是否有相关组件出现了异常的高频重渲染,全局状态是否如预期刷新。
实际开发案例分享
eg1
性能优化上笔者有这么一个实际处理过的 BUG 作为例子,之前处理过一个 React 移动端项目(页面顶部)导航栏组件的异常(高频)重渲染问题,这个组件的效果表现为:随用户的页面向下滚动而自动收起,随用户的页面向上滚动而自动弹出。很好理解的,我们需要获取当前的页面滚动数值,和之前记忆缓存的滚动数值做比较,以判断是否展开或收起导航栏,但是在代码中,对页面滚动值的更新记忆使用了 React#useState ,但实际这一定是有问题的(有性能隐患)的,因为 scroll 本身一定是高频触发事件,就会导致 setState 的频繁调用,而 setState 是关联 React 的再渲染行为的,那么也就导致了异常(高频)重渲染问题。而解决方式也很简单,React 为开发者提供了无关渲染的可用于存储变量值的 api#useRef。
eg2
在某页面组件源码中,其为了实现较为复杂的交互效果,使用了在 scroll 高频事件触发下使用 selfDom#getBoundingClientRect() 以获取自身元素的尺寸信息,并以此计算更新相关变量值 —— 虽然页面表现正常,但是因为 getBoundingCLientRect 的调用可能会使浏览器为了获取指定元素的准确尺寸(以及定位)信息,而潜在的对页面重排,最终实际表现为:页面随滚动出现了卡顿(掉帧)行为。笔者对此的优化,是适当对原本复杂的交互效果做出取舍,弃用了在 scroll 事件回调函数中调用 getBoundingClientRect 。
还是再回到第一个维度,从时间空间上分类,简要梳理一些笔者了解的性能优化手段,(其中部分是实际运用过的,而也有小部分仅了解,但也能在需要时学习上手的):
空间优化
- 对于一些图片类型的资源文件,
- 对其压缩体积,比如使用 tinypng 等工具类网站
- 考虑将其转格式为更流行的 webp 、avif 格式,(当然也有前提:需要考虑用户浏览器是否能兼容这些格式文件,如果不能兼容,则还是兜底返回更通用格式的资源文件)
- 代码打包构建时,确定摇树优化的特性是否正确触发,帮我们去除一些项目中未被实际引入的文件(模块)
- 也是这一点相关联的,可以配置 eslint 等格式化工具,对于一些(引入模块但未使用的) import 语句自动删除,辅助摇树优化使用
- 代码拆分,比如原本打包部署后,是独一份 index.min.js 文件,而预先的作代码拆分处理,可能最终代码产物被拆分为多份 js 文件: home.min.js 、 user.min.js、login.min.js, 当用户访问相关页面时,只请求相关联的 [name].min.js 文件
- 几种加载模式
- 延迟加载/懒加载
- 比如对于一些不是用户第一时间需要交互的组件,在代码中改为 lazyImport 声明调用
- 比如对于一些长页面下,不在视窗范围内的图片等资源,改为懒加载模式
- 按需加载
- 基于代码分割技术,也是上面提到的: 当用户访问相关页面时,只请求相关联的 x.min.js 文件
- 分层加载(图片例子
- 比如目录页下多张图片显示,这里请求的是图片的低分辨率小体积的缩略图。而在进入详情页后,才请求图片的高清资源
- 分层加载策略下的:渐进加载(也是图片例子
- 先加载显示图片的 hash 缩略图(当前流行的方案是:blurHash、thumbHash),而后图片完整资源加载完成时再切换显示完整图片(这一点可能并不算性能范畴的优化,而是提升用户体验的一点)
- 预加载
- 对于指定的资源文件,指定为预加载模式,让用户感知为:页面点击即没有等待,完整显示。
<link ref="prefetch" href="min.js" />
- 对于指定的资源文件,指定为预加载模式,让用户感知为:页面点击即没有等待,完整显示。
- 延迟加载/懒加载
- 一些体积较大的库选择不打包进 bundle.js 文件,转而单独加载
- 比如对于 react 、lodash 这样的库,可以配置打包行为,为编译出的 html 文件添加相关 link 标签,从 CDN 或自定义服务器上单独请求 react.min.js 、 lodash.min.js 等文件
- 资源压缩:在客户端支持解压缩前提下,请求返回压缩后的资源(比如 gzip 压缩方案)
- 场景允许下,当采用 SSR 渲染方案,具体到 React 框架下,在规范使用其服务端组件时,能够减少 client 端需请求的相关 bundle.js 文件体积,同时也降低水合比较成本。
时间优化
-
提升点主要表现为让资源: 传输更快、或者加载更快。
-
CDN 平台使用,指定一些资源被缓存到 CDN 分发平台(分发节点),减少服务器资源文件与客户端的物理距离,一定程度减少用户的请求时间;
- 关于这一点,因为涉及到 CDN 节点的流量计费场景,需要注意配置访问频率限制、用量封顶配置、监控报警
-
浏览器缓存方案,对于一些重复请求,通过强缓存或者协商缓存直接使用本地已缓存文件
- 也是缓存这个大类别下,在服务端侧,还有对于数据查询处理的 redis 缓存方案(仅模糊了解,反正最终也是服务于减小服务器查询压力、同时一定程度提升请求响应时间的)
-
使用协议:比如使用 HTTP2.0 ,支持 TCP 连接的多路复用
- 对比 HTTP 1.0 , 因为多个请求是串行处理,可能出现队头阻塞问题
其他优化
不能简单的归类为上面的时间、空间维度的优化行为:
- 比如妥善使用框架提供的 memoAPI (React 下 useMemo/useCallback/memo), 规避无必要的页面重复渲染行为。
- 以及 React 下,明确一些变量值如果无关渲染更新,那么应使用 useRef 替代 useState
- 请求合并,以跳过浏览器对并发请求数的限制,比如后端有提供一个聚合接口
- 以及明确一些页面的请求行为,比如在一些穿透详情页,考虑将请求结果缓存为全局状态,则后续再次点击进入同一页面、或者点击进入关联页面时不用再额外请求服务端(当然如此就需额外的处理刷新时机)
可能不能算在性能优化范畴,更多的是用户体验上,比如:
- 有意的对部分组件做 loading 状态下的显示处理(一类方案如骨架屏),缓解用户的等待焦虑;
- 对图片宽高尺寸的提前声明,减少页面的布局偏移
- 以及更古早的优化点:和 CSS 基础掌握相关,能用 transform#translateY 实现的动效效果就不应使用 marginTop 来实现,这一点关联浏览器的重排、重绘行为
关于(前端)性能优化的个人简短梳理总结
https://infen.cc/loc-blog/31_fe-performance-optimization[Copy]转载或引用本文时请遵守“署名-非商业性使用-相同方式共享 4.0 国际”许可协议,注明出处、不得用于商业用途!分发衍生作品时必须采用相同的许可协议。