前端性能优化指南

前端性能优化指南

本文档使用AI工具将原始的手工笔记进行了整合, 并由AI进行评审后统一修正、重组与补全。


目录

  1. 优化总览
  2. 性能指标体系
  3. 网络传输优化
  4. 渲染层优化
  5. 缓存策略
  6. 架构与基础设施
  7. 移动端专项优化
  8. 附录:Web 字体

1. 优化总览

前端性能优化围绕四条主线展开:

优化方向 目标 核心手段
减少网络交互次数 降低 RTT 开销 资源合并、缓存、协议升级
减少传输内容大小 降低带宽消耗 压缩、精简、按需加载
优化渲染路径 加速首屏呈现 关键渲染路径优化、异步加载
架构与基础设施 全局提效 CDN、HTTP/2、边缘计算

2. 性能指标体系

架构师点评: 原文档使用自造术语,此处统一映射到 Web Vitals 标准指标。

2.1 核心 Web Vitals(Google 标准)

指标 原文映射 含义 目标阈值
FCP (First Contentful Paint) 首次渲染 浏览器从白屏到首次绘制任何内容 ≤ 1.8s
LCP (Largest Contentful Paint) 首次有意义渲染 [1] 视口内最大内容元素渲染完成 ≤ 2.5s
TTI (Time to Interactive) 可交互时间 页面完全可交互(主线程空闲 ≥5s 窗口) ≤ 3.8s
TBT (Total Blocking Time) FCP 到 TTI 之间主线程被长任务阻塞的总时长 ≤ 200ms
CLS (Cumulative Layout Shift) 页面生命周期内的累计布局偏移 ≤ 0.1
INP (Interaction to Next Paint) [2] 用户交互到下一帧绘制的延迟 ≤ 200ms

[1] FMP(First Meaningful Paint)已被 Lighthouse 弃用,改用 LCP。
[2] INP 于 2024 年 3 月取代 FID 成为 Core Web Vitals 指标。

2.2 其他参考指标

指标 含义
Visually Complete 视口内所有内容可见(原始文档中的"视觉完整")
Speed Index 页面内容可见填充速度的综合得分
TTFB (Time to First Byte) 首字节时间,反映服务端响应速度

3. 网络传输优化

3.1 减少网络交互次数

(a)请求合并

手段 说明
公共资源提取 将共用 JS/CSS 抽取为公共 chunk,多页面共享,避免重复加载
防止重复加载 确保同一页面内不会多次加载相同脚本(Webpack SplitChunksPlugin / Vite 天然支持)
CSS Sprites 将多张小图合并为一张雪碧图,通过 background-position 定位
小图内联 图片经 Base64 编码后体积 < 5KB 的,可直接内联到 HTML/CSS 中,省去一次请求
按需打包 使用现代构建工具(Webpack / Vite / esbuild)将多个模块打包为有限 bundle,配合 Code Splitting 按路由拆分

(b)协议升级

  • HTTP/2 多路复用:单 TCP 连接上并行传输多个请求/响应,消除 HTTP/1.1 的队头阻塞。
  • HTTP/3 (QUIC):基于 UDP,进一步解决 TCP 层的队头阻塞,连接迁移支持更好。
  • WebSocket: 服务端推送和双向实时通信场景
  • SSE(Server-Sent Events): 服务端主动推送的场景应优先考虑

(c)减少重定向

每次 301/302 重定向消耗一次 RTT,首屏关键路径上应杜绝重定向。

(d)DNS 预解析与预连接

1
2
3
4
<!-- DNS 预解析:提前解析域名 -->
<link rel="dns-prefetch" href="//api.example.com" />
<!-- 预连接:提前完成 DNS + TCP + TLS -->
<link rel="preconnect" href="//cdn.example.com" />

3.2 减少传输内容大小

手段 说明
Gzip / Brotli 压缩 文本资源(HTML、CSS、JS、JSON、SVG)开启 Brotli(压缩率优于 Gzip ~20%)
代码精简 去除注释、空白符、死代码(Tree Shaking);生产构建开启 minify
图片格式升级 照片/复杂图 → WebP 或 AVIF(压缩率更高);矢量/图标 → SVG;动图 → APNG 或 Lottie,避免 GIF
视频格式 优先 WebM(VP9 编码)或 AV1,H.264 为兼容兜底
响应式图片 根据设备分辨率/屏幕尺寸返回合适尺寸的图片(srcset + sizes
缩略图 + 渐进加载 先加载低质量缩略图(LQIP),用户交互时再异步加载原图
Cookie 瘦身 不在根域存放过多 Cookie,通过子域隔离;静态资源使用无 Cookie 域名
条件请求 利用 Cache-Control / ETag / Last-Modified 使浏览器在资源未变化时使用 304 响应,避免重复传输

4. 渲染层优化

4.1 关键渲染路径优化

  1. CSS 置于 <head>:让浏览器尽早构建 CSSOM,避免样式表阻塞渲染。
  2. JavaScript 异步加载
    • <script defer>:后台下载,HTML 解析完成后、DOMContentLoaded 前按序执行(推荐)。
    • <script async>:后台下载,下载完立即执行,不保证顺序(适合独立脚本,如统计 SDK)。
    • 传统做法(<script> 置于 </body> 前)可接受,但 defer 更优。
  3. 避免 @import 加载 CSS
    • @import 会使 CSS 文件串行加载(需等父 CSS 解析到该语句时才发起请求),增加关键路径长度。
    • 推荐使用 <link rel="stylesheet"> 并行加载。

4.2 避免渲染阻塞

问题 解决方案
空的 href / src <link href=""><img src=""> 等空属性标签会使浏览器尝试加载当前页面 URL,浪费带宽并阻塞其他资源——发现即删除或填补
首屏无关逻辑 延迟加载(requestIdleCallback / 路由级 Code Splitting)
JS 长任务阻塞渲染 将大计算拆分为多个小任务(Time Slicing),或放入 Web Worker

4.3 减少重排(Reflow)与重绘(Repaint)

  1. 批量 DOM 操作:使用 DocumentFragmentdisplay: none 包裹 → 修改 → 显示,避免多次触发重排。
  2. 避免逐项修改样式:使用 classList 一次性切换类名,而非逐个设置 style 属性。
  3. 用 CSS 动画替代 JS 动画:CSS 动画运行在合成器线程(Compositor Thread),不占用主线程;JS 动画频繁操作 DOM 极易引发重排。
  4. 使用 transformopacity:这两个属性只触发合成(Composite),不触发重排/重绘,是高性能动画的首选。
  5. 不在 HTML 中直接缩放图片<img width="200"> 让浏览器下载原图后缩放,同时可能引发重排;应使用实际尺寸图片或 srcset
  6. 读写分离:避免在同一个帧内穿插读取布局属性(如 offsetHeight)和修改样式,这会强制同步布局(Forced Synchronous Layout)。

4.4 DOM 结构优化

  1. 减少 DOM 深度与数量:过深的 DOM 树增加解析和样式计算成本。单页 DOM 节点数建议 < 1500。
  2. CSS 选择器优化
    • 浏览器从右向左匹配选择器,末尾通配符(*)会导致匹配所有元素后再回溯,应避免:.list * {} ❌ → 使用具体类名 ✅。
    • 减少嵌套选择器层级:.a .b .c .d {} ❌ → .d--specific {} ✅(BEM 等命名约定)。
  3. 关于 <table> 的正确认知

    架构师点评(原文档错误修正): 原始文档建议"尽量避免使用 <table>"。在现代浏览器中,<table> 的渲染性能已大幅改善。表格应用于展示表格数据是完全合理的,不应禁止。真正应禁止的是用 <table> 做页面布局(这是 2000 年代的过时做法)。长表格的分页/虚拟滚动是更值得关注的优化点。

  4. 关于 <iframe>
    • <iframe> 会阻塞父页面 onload 事件;
    • 与父页面共享同域连接池,影响并行加载;
    • SEO 不友好(搜索引擎不易抓取 iframe 内内容);
    • 如需使用,通过 JS 动态设置 src 属性可规避部分问题;
    • 现代场景中,微前端方案(如 Module Federation、qiankun)是对 iframe 的更优替代。

4.5 图片懒加载

推荐使用原生懒加载(Chrome 77+)

1
<img src="photo.jpg" loading="lazy" alt="示例图片" />

传统 JS 方案(Intersection Observer)仅在需要兼容旧浏览器或需要更复杂行为(如占位图过渡动画)时使用。

4.6 脚本优化

  1. DOM 查询优化

    • 优先使用 getElementByIdquerySelector(由快到慢:ID > Class > Tag > 复杂选择器)。
    • 缓存查询结果,避免重复遍历 DOM 树。
    1
    2
    3
    4
    5
    6
    7
    8
    
    // ❌ 不推荐:重复查询
    document.querySelector('#mod .active').classList.remove('active');
    document.querySelector('#mod .inactive').classList.add('active');
    
    // ✅ 推荐:缓存容器引用
    const mod = document.querySelector('#mod');
    mod.querySelector('.active').classList.remove('active');
    mod.querySelector('.inactive').classList.add('active');
    
  2. 静态资源分域存放

    • 浏览器对同域名的并发连接数有限制(HTTP/1.1 下通常 6 个);
    • 将资源分布到不同子域名(如 static1.example.comstatic2.example.com)或 CDN 域名可增加并行下载数;
    • 但 HTTP/2 下此策略反效果——多路复用使单连接即可并行,多域名反而增加 DNS 查询和 TCP 握手开销。仅 HTTP/1.1 用户占比较高时考虑保留。

5. 缓存策略

5.1 页面分类与缓存方案

页面类型 特征 推荐策略
纯静态页面 几乎不更新(如"关于我们") CDN 长缓存 + Cache-Control: max-age=31536000, immutable
纯动态页面 每次/每人看到不同内容 不做页面级缓存;做数据层缓存(Redis / 本地存储)以加速 API 响应
短时静态页面 一段时间内不变,可容忍短期不一致 服务端渲染后缓存为静态文件或写入公共缓存层;更新时主动清除(Cache-Aside 模式)
动静结合页面 同一页面混合静态区域与动态区域 静态部分走 CDN/服务端缓存;动态部分异步请求 + 客户端渲染(SSI / ESI / CSR 组合)

5.2 数据缓存

  • 缓存粒度:JSON 数据、局部 DOM 片段、或 API 响应体。
  • 客户端缓存层:localStorage / sessionStorage / IndexedDB / Service Worker Cache API。
  • 服务端缓存:Redis / Memcached 缓存 API 响应或 DB 查询结果。

5.3 缓存注意事项

  1. 设置合理的过期时间:根据数据新鲜度需求权衡(静态资源用强缓存 + 文件名 Hash 实现永久缓存)。
  2. 主动失效机制:CDN 厂商通常提供 Purge API,内容更新时应主动触发刷新。
  3. 防止缓存击穿 / 雪崩
    • 缓存击穿:热点 key 过期瞬间大量请求直达 DB → 互斥锁 / 永不过期 + 异步刷新。
    • 缓存穿透:请求不存在的数据 → 布隆过滤器 / 空值缓存。
    • 缓存雪崩:大量 key 同时过期 → 过期时间加随机偏移(TTL Jitter)。
    • 缓存踩踏(原文档描述的"第一个无缓存请求时其他阻塞"):又称 Cache Stampede,首请求构建缓存期间其他并发请求应等待而非各自回源 → 使用 Promise 共享或锁机制。
  4. 多端缓存隔离:根据 User-Agent 区分 PC / Mobile / App 内嵌 WebView,分别缓存;或通过不同子域名独立服务。

6. 架构与基础设施

6.1 CDN 部署

  • 静态资源(JS、CSS、图片、字体)部署到 CDN,利用边缘节点就近服务。
  • 选择支持 HTTP/2 和 Brotli 的 CDN 厂商。

6.2 HTTP 协议演进

协议 关键特性 推荐场景
HTTP/1.1 Keep-Alive、管道化(实践中不可靠) 仅兼容性要求时保留
HTTP/2 多路复用、Server Push(已逐步弃用)、Header 压缩 当前主流,推荐默认开启
HTTP/3 (QUIC) UDP 传输、0-RTT 建连、连接迁移 移动端及弱网环境下收益最大

6.3 构建与工程化

  • Code Splitting:按路由/组件拆分代码,首屏只加载必要 bundle。
  • Tree Shaking:消除未使用的模块导出。
  • 资源 Hash:文件名带内容 Hash(如 app.a3f8c.js),配合 max-age=31536000 实现永久缓存,更新时自然失效。
  • 预加载关键资源<link rel="preload"> 提前声明首屏关键 CSS/字体/图片。

7. 移动端专项优化

移动端核心矛盾:网络波动 + 设备性能差异 + 首屏速度敏感

7.1 首屏优化

  1. 控制首屏资源总量:首屏 HTML + 关键 CSS + 首屏 JS 总体积建议 < 200KB(压缩后)。
  2. 首屏静态化 / 服务端渲染(SSR):将首屏 HTML 在服务端生成,避免"JS 加载 → 请求数据 → 渲染"的串行等待链。
  3. 首屏关键资源内联:关键 CSS 内联到 <head><style> 标签中;少量关键 JS 也可内联,避免额外 RTT。
  4. 非首屏内容延迟加载:图片懒加载、路由懒加载、组件按需加载。
  5. 提前推送资源: 在活动开始之前就推送相关静态资源到客户端;

7.2 移动端适配

  • 使用 srcset + sizes 属性根据屏幕宽度返回适当尺寸图片。
  • 触摸交互优化:消除 300ms 点击延迟(<meta name="viewport" content="width=device-width">),使用 touch-action 控制手势行为。

8. 附录:Web 字体

8.1 字体格式选择

格式 压缩率 浏览器支持 推荐
WOFF2 最优(Brotli 压缩) 现代浏览器全覆盖 ✅ 首选
WOFF 较好 几乎全部浏览器 ✅ 兜底
TTF/OTF 无压缩 最广 ❌ Web 不推荐(体积大)
EOT 仅旧版 IE ❌ 已淘汰

8.2 最佳实践

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2'),
       url('/fonts/myfont.woff') format('woff');
  font-display: swap; /* 关键:避免 FOIT(白屏等待字体) */
  font-weight: 400;
}

@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont-bold.woff2') format('woff2'),
       url('/fonts/myfont-bold.woff') format('woff');
  font-display: swap;
  font-weight: 700;
}

关键要点:

  1. font-display: swap:立即使用回退字体渲染文本,字体加载完成后切换。避免不可见文本闪烁(FOIT)。
  2. 子集化(Subsetting):如果只使用部分字符(如中文网站仅常用 3500 字),从字体文件中裁剪出子集,体积可减少 70-90%。
  3. 预加载<link rel="preload" as="font" crossorigin> 提前声明字体下载。
  4. 使用 unicode-range:对不同字重/样式指定不同字体文件,浏览器仅下载实际使用的字符集的字体。
  5. 仅引入所需字重:一个常规(400)+ 一个粗体(700)通常已满足大部分设计需求。

本文档由原始目录 架构设计/6-软件-前端/ 下的五个文件合并、评审与重写而来。原文件中标记为错误的观点已修正,结构已按专业架构文档标准重新编排。

Licensed under CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,知会作者并注明出处
不得用于商业用途!
最后更新于 2026-05-21 00:00 UTC