前端性能优化
优化指标
TTFB (Time To First Byte)
- 定义:从发起 HTTP 请求到接收响应第一个字节的时间。
- 意义:衡量服务器响应速度,影响后续渲染。
- 测量:DevTools Network 面板或 performance.timing。
- 目标:TTFB < 600ms。
- 优化:
- 使用 CDN 或边缘计算(如阿里云 OSS)。
- 启用 HTTP/2 减少连接开销。
FCP (First Contentful Paint)
- 定义:从页面开始加载到页面内容(文本、图片、Canvas 等)首次绘制的时间。
- 意义:反映页面首次视觉反馈的速度,用户感知的加载开始点。
- 测量:使用 Chrome DevTools 的 Performance 面板或 Lighthouse。
- 目标:FCP < 1.8s(良好)。
- 优化:
- 减少服务器响应时间(TTFB):使用 CDN、后端缓存(如 Redis)。
- 优化资源加载:内联关键 CSS、延迟非关键 JS(如
<script defer>
)。 - HTTP/2 推送:推送关键资源(如 app.js、main.css)。
LCP (Largest Contentful Paint)
定义:从页面加载到最大内容元素(图片、文本块、视频等)绘制完成的时间。
意义:反映主要内容加载速度,用户感知的核心体验。
测量:Lighthouse 或 PerformanceObserver(Largest Contentful Paint 条目)。
目标:LCP < 2.5s。
优化:
- 优化关键渲染路径:内联 CSS、预加载字体
<link rel="preload">
。 - 减少 JS 执行时间:优化微任务。
- 服务端渲染(SSR)。
- 优化关键渲染路径:内联 CSS、预加载字体
CLS (Cumulative Layout Shift)
累积布局偏移(CLS)是指页面元素在加载过程中视觉稳定性(加载时跳动)的指标。
定义:页面加载期间视觉元素意外移动的累积分数。
意义:反映页面稳定性和用户体验(避免点击错误)。
测量:Lighthouse 或 LayoutShift 条目。
目标:CLS < 0.1。
优化:
- 设置图片/广告尺寸:
<img width="100" height="100">
。 - 避免动态插入内容:使用 CSS min-height 占位。
- 动画优化:用 transform 避免重排。
- 设置图片/广告尺寸:
TBT (Total Blocking Time)
定义:主线程被长任务(>50ms)阻塞的总时间,影响交互响应。
意义:衡量页面交互卡顿程度。
测量:Lighthouse 或 DevTools Performance。
目标:TBT < 300ms。
优化:
拆分长任务:使用 requestIdleCallback 或 setTimeout。
优化微任务:合并 Promise 链。
测量工具与方法
Chrome DevTools:
- Performance 面板:分析 FCP、LCP、TBT,查看微任务(Promise、MutationObserver)和渲染时间。
- Network 面板:检查 TTFB、资源加载时间。
Lighthouse:
- 提供 FCP、LCP、TTI、TBT、CLS 和 SI 的综合报告。
- 运行:DevTools Lighthouse 面板或 npm run lighthouse。
WebPageTest:
- 分析多地域性能,提供 Speed Index 和详细时间轴。
Performance API:
网络优化
DNS 预解析
提前解析域名对应的 IP 地址,减少延迟。
- dns-prefetch
提前进行 DNS 解析
- preconnect
。通过抢先执行部分或全部握手(HTTP 为 DNS+TCP,HTTPS 为 DNS+TCP+TLS),预连接可加快未来从给定源加载的速度。
- 实现示例:
<link rel="dns-prefetch" href="https://example.com">
<link rel="preconnect" href="https://example.com">
- 注意事项:
如果一个页面需要与许多第三方域建立连接,将它们全部预连接可能会适得其反。 <link rel="preconnect">
提示最好只用于最关键的连接。 对于其他连接,只需使用 <link rel="dns-prefetch">
,以节省第一步 DNS 查询的时间。
资源预加载
提前加载页面需要的资源,避免后续请求。
- preload(立即加载,马上要用)
<link rel="preload" href="/static/main.css" as="style">
<link rel="preload" href="/static/main.js" as="script">
as 属性告诉浏览器资源类型,方便正确应用缓存策略和优先级。
适合「当前页面」马上需要的关键资源。
- 首屏关键 CSS、JS
- Web 字体文件(防止字体闪烁)
- 首屏大图(如背景图)
- prefetch(稍后加载,可能用得到)
<link rel="prefetch" href="/page2.js">
适合「下一页面」可能要用的资源(浏览器空闲时低优先级下载)。
prefetch 不会立即执行,而是缓存下来,下次用户进入对应页面时可直接使用。 非常适合 单页应用(SPA) 的路由级懒加载场景。
HTTP/2 优化
HTTP1主要存在以下问题:
- 同一域名下连接数有限(一般最多 6 个)
- 队头阻塞(前一个请求未返回,后续阻塞)
- 无法多路复用(每个请求都要建立连接)
HTTP/2 优化
优化特性 | 说明 |
---|---|
多路复用 (Multiplexing) | 一个 TCP 连接可同时发送多个请求/响应,避免队头阻塞。 |
二进制分帧 (Binary Framing) | 把数据分成帧并标识来源,传输更高效。 |
头部压缩 (HPACK) | 减少 HTTP Header 体积。 |
服务器推送 (Server Push) | 服务器可提前推送客户端可能需要的资源。 |
CDN 加速
CDN(内容分发网络)是指将网站的静态资源(如图片、字体、CSS、JavaScript等)分布到全球多个节点,使用户从最近的节点获取资源,提高加载速度。
Gzip 与 Brotli 压缩
Gzip 与 Brotli 都是常用的压缩算法,用于减小文件大小,提高加载速度。
- Gzip:由 GNU 项目开发,压缩率较高,支持所有浏览器。
- Brotli:由 Google 开发,压缩率更高,支持现代浏览器(如 Chrome、Firefox)。
当浏览器发起 HTTP 请求时,会在请求头中带上:
Accept-Encoding: gzip, deflate, br
当浏览器发起 HTTP 请求时,会在请求头中带上:
Content-Encoding: br
或
Content-Encoding: gzip
静态压缩资源
一般是通过构建工具(如 Webpack、Rollup 等)在打包时自动压缩资源。
- Webpack:使用
compression-webpack-plugin
插件压缩资源。 - Rollup:使用
@rollup/plugin-terser
插件压缩资源。
动态压缩资源
nginx 也可以开启压缩功能,将压缩后的资源返回给客户端。
nginx配置
http {
# ----------------------------
# 启用 Gzip 实时压缩
# ----------------------------
gzip on;
gzip_vary on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types
text/plain
text/css
application/javascript
application/json
application/xml
application/vnd.ms-fontobject
application/x-font-ttf
font/opentype
image/svg+xml;
gzip_proxied any;
# ----------------------------
# 启用 Brotli 实时压缩
# (需安装 ngx_brotli 模块)
# ----------------------------
brotli on;
brotli_comp_level 6;
brotli_types
text/plain
text/css
application/javascript
application/json
application/xml
image/svg+xml
font/woff2;
# ----------------------------
# 启用预压缩文件支持
# ----------------------------
gzip_static on; # 优先使用 .gz 文件
brotli_static on; # 优先使用 .br 文件
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
# 让 Nginx 自动判断浏览器是否支持 br / gzip
# 并优先返回对应的预压缩版本
try_files $uri$br $uri.br $uri.gz $uri =404;
# 若无预压缩文件,则实时压缩
add_header Vary Accept-Encoding;
}
}
}
注意
- 压缩等级 : 通常设置为 5~7;过高压缩等级影响构建速度
- 压缩对象 : 仅压缩文本资源(HTML/CSS/JS/JSON),不压缩图片、视频等
- 预压缩推荐 : 对静态资源(CDN、Nginx)使用预压缩,而非实时压缩
- 同时提供两种格式 :
.br
优先,.gz
兼容老浏览器 - HTTP/2/3 兼容 : Brotli 在 HTTP/2、HTTP/3 中表现更优(头部压缩更好)
- 验证压缩是否生效 : 使用浏览器 DevTools → Network → 查看
Content-Encoding
减少 HTTP1.1 请求次数
每个 HTTP 请求都包含以下开销:
- DNS 解析(域名 → IP)
- TCP 连接建立(三次握手)
- TLS 握手(HTTPS)
- 首包延迟(TTFB)
- 数据传输和响应解析
减少请求常用策略
- 静态资源合并(Bundle / Merge)
把多个文件合并成一个请求,适用于 JS、CSS、图标。
- 打包工具(如 Vite、Webpack)自动合并依赖。
- CSS @import 尽量减少嵌套。
- JS 按模块打包或动态导入。
- 图片优化:使用合图与现代格式
(1)雪碧图(Sprite) 把多个小图标合并为一张大图,通过 CSS 定位显示。
.icon {
background: url(sprite.png) no-repeat;
}
.icon-user { background-position: -10px -20px; }
(2)使用 Icon Font 或 SVG Sprite
- Icon Font:用字体代替图片图标;
- SVG Sprite:多个图标打包成一个 SVG 文件。
<svg><use href="#icon-search"></use></svg>
(3)使用 现代图片格式
WebP / AVIF 比 PNG、JPEG 小得多,可大幅减少传输体积。
- 资源内联(Inlining)
思路:把小型资源直接嵌入 HTML 或 CSS 文件中,省去单独请求。
(1)Base64 内联图片
.icon {
background: url('data:image/png;base64,...');
}
base64 编码后的图片大小约为原始大小的 33%,因此在资源量不大的情况下,使用 base64 编码是一种不错的选择。
(2)内联 CSS / JS
<style>
body { color: #333; }
</style>
<script>
console.log('init');
</script>
建议:仅限于 小文件(<10KB),否则 HTML 会变大影响首屏。
- 使用 HTTP/2 或 HTTP/3 多路复用
HTTP/1.1 每次请求一个连接; HTTP/2/3 允许一个连接并发多个请求,极大降低连接开销。
在 HTTP/2 环境下:
- 小文件拆分成本降低;
- 但仍要减少不必要的请求(如重复依赖)。
为什么 HTTP/2 改善了请求问题
HTTP/1.1 的问题:
- 一个 TCP 连接同一时刻只能处理一个请求;
- 多个资源会触发队头阻塞(Head-of-line blocking);
- 浏览器通常会为一个域名开多个连接(一般 6 个)。
HTTP/2 改进:
- 多路复用(Multiplexing):一个 TCP 连接可以同时传输多个请求;
- 头部压缩(HPACK):减少请求体积;
- 服务器推送(Server Push):可提前推送资源;
- 二进制分帧:更高效传输。
浏览器缓存
浏览器缓存是指浏览器在本地存储已下载的资源,当用户再次请求相同资源时,直接从缓存中获取,而不是从服务器重新下载。这可以减少网络请求次数,提高加载速度。
缓存策略
- 强缓存:浏览器直接从缓存中获取资源,不向服务器请求。通过设置
Cache-Control
和Expires
头来实现。 - 协商缓存:浏览器向服务器请求资源,服务器根据资源的最后修改时间判断是否返回资源。通过设置
Last-Modified
和ETag
头来实现。
Service Worker 缓存
Service Worker 是一种运行在浏览器后台的脚本,它可以拦截和处理网络请求,实现缓存、离线访问等功能。通过注册 Service Worker,可以对静态资源进行缓存,提高加载速度。
打包构建
类别 | 优化策略 |
---|---|
体积优化 | Tree Shaking、压缩、按需加载、图片优化 |
加载优化 | Code Splitting、Preload/Prefetch、HTTP/2 |
缓存优化 | hash 文件名、合理 Cache-Control |
构建优化 | 使用 Vite、并行压缩、缓存构建结果 |
优化技巧
总体优化方向
优化方向 | 关键措施 |
---|---|
DOM 操作 | 减少操作次数,避免强制同步布局 |
事件处理 | 节流/防抖,事件委托 |
动画渲染 | GPU 加速、使用 transform、will-change |
数据逻辑 | 缓存结果、延迟计算、使用 Worker |
内存优化 | 清理引用、解绑事件、虚拟列表 |
框架优化 | Vue 的 computed、React.memo 等 |
DOM 与渲染优化
优化项 | 检查要点 | 示例 |
---|---|---|
减少 DOM 操作次数 | 批量修改后一次插入 | 使用 DocumentFragment 合并插入节点 |
避免频繁读取布局信息 | 先读后写,避免强制重排 | 不要在修改样式后立即读取 offsetWidth 、getBoundingClientRect() |
样式批量修改 | 一次性更改 class 或使用 CSS 变量 | element.classList.add('active', 'show') |
避免重绘(Repaint) | 控制视觉变化属性 | 优先使用 transform 、opacity 代替 top 、left |
图片尺寸固定 | 避免布局抖动 | <img width height> 提前定义大小 |
事件与交互优化
优化项 | 检查要点 | 示例 |
---|---|---|
节流与防抖 | 高频事件使用 throttle/debounce | 滚动、输入、窗口 resize |
事件委托 | 列表、表格、动态元素用事件委托 | 父元素代理子元素点击 |
避免阻塞事件循环 | 不在事件中执行重计算 | 将耗时任务丢给 Web Worker |
动画与过渡优化
优化项 | 检查要点 | 示例 |
---|---|---|
使用 CSS 动画 | 优先使用硬件加速属性 | transform 、opacity |
启用 GPU 加速 | 使用合成层 | transform: translateZ(0) 或 will-change |
控制动画复杂度 | 不要同时改变 layout 属性 | 避免频繁操作 width/height/top/left |
使用 requestAnimationFrame | JS 动画同步浏览器帧率 | 替代 setInterval |
脚本执行优化
优化项 | 检查要点 | 示例 |
---|---|---|
异步加载非核心脚本 | 使用 async 、defer | <script defer src="..."> |
延迟执行任务 | 使用 requestIdleCallback | 在空闲时处理非关键逻辑 |
缓存计算结果 | 重复计算结果记忆化 | memo[key] = expensiveCalc() |
减少对象深层拷贝 | 控制 JSON.parse/clone | 使用结构共享或浅拷贝 |
数据与内存优化
优化项 | 检查要点 | 示例 |
---|---|---|
虚拟列表渲染 | 大数据列表只渲染可视区域 | 使用 Vue Virtual Scroll / react-window |
避免内存泄漏 | 清除定时器、解绑事件 | clearInterval() 、removeEventListener() |
减少无效缓存 | 控制缓存大小 | 限制 localStorage/sessionStorage 使用量 |
避免重复数据计算 | 缓存接口结果 | useMemo 、computed 、或本地 Map 缓存 |
框架级优化
优化项 | Vue | React |
---|---|---|
渲染控制 | 用 v-show 代替频繁切换的 v-if | 拆分组件、减少 diff 范围 |
计算缓存 | computed 、watch 合理使用 | useMemo 、useCallback |
避免不必要响应式 | 不将静态数据放入响应式系统 | 使用普通变量存放固定数据 |
懒加载与异步组件 | defineAsyncComponent() | React.lazy() + Suspense |
图像与多媒体优化
优化项 | 检查要点 | 示例 |
---|---|---|
使用合适格式 | WebP、AVIF 优先 | <img src="a.webp" /> |
按需加载 | 懒加载图片 | <img loading="lazy"> |
压缩图片 | TinyPNG / imagemin | 构建时自动压缩 |
控制视频体积 | 分辨率与码率合理 | video 加上 preload 控制 |
调度与任务管理
优化项 | 检查要点 | 示例 |
---|---|---|
任务分片 | 拆分大计算任务 | 分块执行,避免主线程卡顿 |
使用 Worker | 密集计算放入 Worker | 避免阻塞 UI 渲染 |
优先级控制 | requestIdleCallback / RAF | 非关键逻辑延迟执行 |
重点原则总结
- 能不操作 DOM 就不要频繁操作
- 能异步就不要同步阻塞主线程
- 能缓存的结果就不要重复计算
- 能复用的组件就不要频繁销毁重建
- 能交给浏览器的(CSS动画/GPU加速)不要手写 JS 实现