- Published on
前端性能优化
- Authors

- Name
- Li WenKang
- https://x.com/liwenkang_space
前端性能优化指的是,通过各种技术手段,提高网页的加载速度,渲染速度,交互响应速度,减少资源占用,提高用户体验。
按照测量在先,优化在后的原则,我将从以下几个方面进行分析:
- 性能检测
- 网络请求优化
- 资源加载优化
- 代码与架构优化
- 渲染性能优化
- 感知体验优化
性能检测
- 确立性能指标与目标:关注核心 Web 指标(Core Web Vitals):
- LCP(最大内容绘制):衡量加载速度,建议小于2.5秒。
- FID(首次输入延迟):衡量交互性,建议小于100毫秒。
- CLS(累积布局偏移):衡量视觉稳定性,建议小于0.1。
- 性能监控流程化:
- 实验室监控:使用 Lighthouse、WebPageTest 等在开发阶段模拟测试。
- 真实用户监控(RUM):在生产环境通过 Performance API 收集真实用户数据,分析性能瓶颈。
- 性能优化迭代:性能优化是持续过程。定期(如每季度)通过监控数据复盘,针对退化或新瓶颈制定优化方案。
网络请求优化
- 合并重复请求,减少请求数量
- 在 SaaS 系统中,经常有系统配置参数和用户配置参数。可以把分散在各个组件的配置参数查询归一化处理,在用户登录后统一请求,并存入 localStorage 中进行缓存(后续使用时直接读取,避免重复查询,注意涉及鉴权类信息不可存入,有安全隐患)
- 针对大量小图标,可以考虑合成一张图,使用时通过 CSS控制显示不同的部分。但在 HTTP2多路复用的环境下,需权衡合并的收益和缓存颗粒度的影响
- 服务端开启 GZIP 压缩 开启压缩后,可大幅减少请求包的大小,节省带宽,减少服务器端压力
- 静态资源使用 CDN 加速 开启 CDN 加速后,用户可以就近访问
- 使用 HTTP2,以及配置相应的 http 缓存,避免重复下载 升级到 HTTP2,可利用其二进制分帧和多路复用特性,极大提高请求的并发数量。 针对品牌图标,活动 banner 等资源,通过打包时将文件名称带上 hash 值后,可以设置强缓存(Cache-Control: max-age 设置一年) 针对容易变化的资源,可以设置 Etag,走协商缓存 http1.1 时代,为了突破浏览器并发请求数量,会把静态资源放到不同的域名下。升级到 HTTP2 之后,则要避免此操作,避免多余的DNS 查询和 TCP 连接开销
- 预连接与DNS预解析:使用 dns-prefetch和 preconnect资源提示。dns-prefetch提前解析第三方资源的域名,preconnect则提前建立与服务器的连接(包括DNS查询、TCP握手、TLS协商),对关键第三方资源特别有用。
<!-- 提前建立与服务器的连接 -->
<link rel="preconnect" href="https://cdn.example.com" />
<!-- 提前解析第三方资源的域名 -->
<link rel="dns-prefetch" href="https://cdn.example.com" />
资源加载优化
- 静态资源压缩 在生产环境下,通过 uglifyJsPlugin 等插件压缩。 引入三方库时,注意使用其生产版本
- 字体、图片,优先使用高压缩,高质量的格式,并做优雅降级 字体:woff2 => woff => ttf 使用 font-display: swap:此CSS属性使文字在自定义字体加载完成前先显示系统字体,加载后再切换,有效避免因字体阻塞渲染导致的文字不可见 对首屏渲染至关重要的字体,可使用
<link rel="preload">提前加载。 图片:avif => webp => jpeg/png 注意svg类图片也要压缩 图片还可以通过使用<picture>元素和 srcset属性,根据设备屏幕大小、像素密度等因素提供最合适的图片尺寸 - 按需加载,Tree Shaking 有了 ESM 的 import/export 后,Webpack 可以在编译阶段分析依赖关系,从而进行 tree shaking,移除未使用的代码 针对大型组件,可以考虑做 Code Split,然后做懒加载
代码与架构优化
- 轻量库替换 比如用 day.js 替换 moment.js
- 去除不必要的依赖/插件,保持依赖库的更新 将已废弃的依赖,webpack 插件及时移除,并保持依赖库的更新(新版本的依赖通常会解决安全问题,拥有更好的性能,但也要注意兼容问题)
- JavaScript/Css 代码层面的优化
- 避免在循环中操作 DOM,可以考虑使用虚拟 DOM 先处理完成后,找出需要在真实 DOM 上操作的最小集。或者使用 DocumentFragment,在内存中构建好 DOM 后,一次性插入到真实 DOM 中,仅触发一次重绘重排。
- 可以将耗时较长的任务放入宏任务中处理(setTimeout/requestAnimationFrame),涉及大计算量的部分,可以放入 web worker 中处理
- 使用浏览器缓存(localStorage/SessionStorage/Cookie/IndexDB)。React 代码层面可以使用 useMemo,useCallback。可以使用 ES6 的单例模式,在内存中实现数据的暂存和消费。注意 localStorage 是同步操作,应避免存储过大数据,频繁写入读取也会阻塞主线程
- 注意 组件/定时器/事件监听 的正确销毁,避免内存泄露
- 如非业务需要,默认使用 Promise.all 并发接口请求
- DOM 查询优化,尽量使用准确的元素查询器,默认是从右向左解析的,减少嵌套,慎用 querySelectorAll 查询大批量 DOM
- 使用 Vite 等新兴构建工具利用 ES Module 和原生技术,在启动速度和热更新上有显著优势,能极大提升开发体验和构建效率
渲染性能优化
- 懒加载:比如目录树的多层结构,ifame 等较重资源,也可以使用 loading="lazy" 处理
- 虚拟列表:只渲染可视窗口部分的 DOM。比如目录树的数量过多,列表页面的数量过多
- 优化渲染逻辑,避免多余的重绘重排(1. 如果需要修改 DOM 样式,可以一次性修改 className,而不要每次修改一个属性;2.避免在修改 DOM 样式后,立即读取 DOM 涉及位置,布局的属性,比如 getComputedStyle,会导致强制同步;3. 开启 GPU 加速,利用 transform translate ,opacity 等属性,触发合成层更新;4. 对于复杂的动画 DOM,可以添加 will-change,告知浏览器将会变化;5. 如果一个 DOM 需要频繁修改,可以先设置 display:none,待修改全部完成后,再改为 block,减少重绘重排;6. 针对复杂的动画 DOM,可以通过设置 position:fixed, absolute,让它脱离正常文档流,避免影响其他部分)
- 表格渲染优化:在表格中,单元格的高亮可以通过 css 实现,不要用 JavaScript 实现,可考虑数据分页,区分表格列的渲染优先级(优先展示数据列,然后再展示操作列)。当鼠标移入单元格后,再加载 tooltip/popConfirm 组件。利用 shouldCellUpdate 等方法,确保只有当单元格数据变化的时候,才触发刷新。注意如果数据的格式化计算较复杂时,也可以考虑将计算放在后端处理
- 超大流量页面优化:比如针对发布会当天的活动页面,可以采用静态页面直接展示,不启用编辑器的协同服务,只关注核心功能可用
感知体验优化
- Loading动画(全局,图片),此处需要注意占位图的宽高要设置明确,避免页面闪烁。进度条展示当前任务处理进度。
- 始终有兜底的错误提示信息,避免页面白屏