# 概述
# 性能优化步骤
针对前端全链路性能优化专题,我们依次做如下几个关键步骤
确定优化范围和关键路径
拆开并定义关键指标
计算、收集、分析指标数据
执行具体优化方案
对比分析调整优化结果
灰度测试并完善数据指标
输出优化后完整的数据报表
# 关键指标的设定
- 自动化检测
- 通过Lighthouse
- Performance 面板
- Chrome DevTools Protocol
- 手动检测 - Performance API
LCP: 加载性能。最大内容绘制应在 2.5s 内完成。
FID: 交互性能。首次输入延迟应在 100ms 内完成。
CLS: 页面稳定性。累积布局偏移,需手动计算,CLS 应保持在 0.1 以下。
# 加载
首屏时间=DOMContentLoaded 时间=domContentLoadedEventEnd-fetchStart
网络环境采集
两张不同尺寸的图片 下载时间/文件体积 求平均值
- 弱网优化
白屏
- 白屏时间 FP= domLoading-navigationStart
# 交互
- 手动录制
- 自动化用 adb 录制视频;adb shell screenrecord --time--limt 10/sdcard/perf.mp4
- 白屏响应时间=白屏最后- 帧的时间- 点击时的起始帧时间
- 首屏加载时间=内容完全加载出来那一帧的时间-点击时的起始帧时间
- 弱网环境测试
- 2G/3G/4G 各种特殊场景测试
- 采用分帧计算
# 渲染
# 构建
- 通过 Webpack speed-measure-webpack-plugin 插件测量构建时间
拿到各阶段的loader和plugin插件执行占用时间
- 通过 Webpack webpack-bundle-analyzer 测量构建体积
拿到各js、css、html等文件大小
# 指标收集
# 收集方式
用PerformanceAPI
使用三方库web-vitals
使用三方库sentry
# 上送方式
通过sendBeacon上报
通过gif
# 上送策略
数据过滤
数据抽样
socket 心跳链接,实时上送
http 满足30条上送,断点续传
弱网时候先存本地,强网时候立即上送
# 加载过程优化(首屏秒开)
# 网络方面
DNS优化
- NDS缓存
- DNS预解析(dns-prefetch)
使用http2.0
- 多路复用,在浏览器可并行发送 N 条请求。
- 首部压缩,更小的负载体积。
- 请求优先级,更快的关键请求
使用CDN
利用缓存
- 强缓存(Last-modefiy)
- 协商缓存(Etag)
开启Gzip/brotli
# 服务端渲染
SSR/SSG
PWA离线缓存
ServiceWorker
# 懒加载/预加载
- prefetch
prefetch (预获取):浏览器空闲的时候进行资源的拉取
- preload
preload (预加载):提前加载后面会用到的关键资源 ⚠️ 因为会提前拉取资源,如果不是特殊需要,谨慎使用
路由懒加载,无需加载整个应用的资源
降低白屏时间:
- DNS 走缓存
- 开启 Dns 预解析 x-dns-prefetch-control
- dns-prefetch 强制对 域名做预解析
- 端内:提前初始化 WebView 并且加载域名开始解析,端外:iframe
- 使用骨架屏幕, Cli+puppeteer
# Hybrid方面
离线加载
- 创建预置(Js/css/html)压缩后的离线包
- 更新diff patch包
预渲染(NSR渲染)
WebView预加载
- native端预初始化Webview组件并隐藏,使用时候直接展示
WebView池化
IOS使用WKWebview
- 降低内存占用
弱网优化
- 接口合并
- 图片尽量用base64
- 不自动加载图片,只显示本占位图
根据手机品牌(内存、cpu性能)
- 优化策略
- 时间换空间(内存小)
- 空间换时间(cpu差)
# 交互过程优化
# 网络请求
请求前置
请求合并
请求过滤(防重复)
- cancelToken
请求资源缓存
- react-query
- swr
# 编码优化
- 利用算法
- object.freeze
- 使用多线程Webworker
# 组件加载
- 组件懒加载
- 长列表
- 分页
- 虚拟滚动
- 图片懒加载
- 进入视口后加载:使用IntersectionObserver API
- 使用新的属性: loading=lazy
- 长列表
- 组件预加载
# 解决卡顿
FPS 在 60 以上,单帧渲染耗时
fps_compatibility 表示兼容性方面的处理
使用requestanimationframe/requestIdleCallback
使用Webworker
降低卡顿:
数据相关,找后端协调
主线程和合成线程调度问题;合成线程主要用于绘制 利用 transform
空间换时间,时间换空间 ,复杂任务拆分为多个任务
在对 dom 元素增删对过程中最好在 DocumentFragment 上操作,而不是直接在 Dom 上操作
# 渲染
preload/prefetch
prerender-spa-plugin 预渲染
defer/async
防抖/节流
重绘/回流
# 构建过程优化
# 优化打包体积
压缩
- gzip压缩/brotli
- js (terserPlugin)
- html(html-webpack-plugin)
- css (optimizeCssAssetsWebpackPlugin)
- 图片压缩(image-webpack-loader)
剔除无用代码
- js Tree Shaking: 无用导出将在生产环境进行删除
- css (MinicssExtractPlugin,PurgessWebpackPlugin)
更小的图片体积
- webp
- avif
- 更合适的尺寸: 当页面仅需显示 100px/100px 大小图片时,对图片进行压缩到 100px/100px
- 更合适的压缩: 可对前端图片进行适当压缩,如通过 sharp 等
代码分割
- 路由懒加载( import)
- bundle spliting
- 入口点分割(多页应用)
- 提取公共代码
- 使用CDN包
借用WASM其他语言开发工具链
# 优化打包速度
缩小打包范围
- exclude/include
- alias
- extensions
- resolverLoader
- modules
利用缓存
- 开启babel-loader缓存
- cache-loader
- hard-source-webpack-plugin
编译提速
- browserlist/babel: 及时更新 browserlist,将会产生更小的垫片体积
- Esbuild(go)
- SWC(rust)
开启多进程
- thread-loader
- happyPack(弃用)
预编译
- DllPlugin
- DllReferencePlugin