前端的性能优化有哪些?
面试速答(30 秒版 TL;DR)
前端性能优化不要背零散技巧,面试里更好的表达方式是按链路拆:
- 网络与传输层:减少请求耗时和传输体积,比如 CDN、HTTP 缓存、Gzip/Brotli、DNS 预解析、
preconnect。 - 资源加载层:让关键资源更早到,非关键资源更晚到,比如代码分割、懒加载、
preload、图片懒加载、字体优化。 - 渲染层:减少主线程压力,缩短从 DOM/CSSOM 到 Paint/Composite 的时间,比如关键 CSS、减少阻塞 JS、避免频繁回流重绘、动画优先
transform/opacity。 - 运行时层:减少无效计算和无效渲染,比如防抖节流、虚拟列表、Web Worker、组件更新边界优化。
- 工程与监控层:做体积治理、性能预算、指标采集、线上监控,避免“优化一次,回退三次”。
面试结论可以压成一句话:
前端性能优化的本质,就是让“关键内容更早出现、交互更快响应、资源更少浪费、性能退化可被持续发现”。
先建立心智模型:性能到底在优化什么?
前端性能不是单一指标,它通常至少包含四类目标:
| 目标 | 用户感知 | 常见指标 |
|---|---|---|
| 加载快 | 页面尽快显示内容 | FCP、LCP、TTFB |
| 交互快 | 点击后尽快响应 | INP、TBT |
| 渲染稳 | 页面不乱跳、不闪烁 | CLS |
| 运行省 | 不容易卡顿、发热、爆内存 | FPS、内存占用、长任务数量 |
现代项目常用 Core Web Vitals 作为统一口径:
- LCP(Largest Contentful Paint):衡量主要内容出现得够不够快。
- INP(Interaction to Next Paint):衡量用户交互后的整体响应延迟。
- CLS(Cumulative Layout Shift):衡量页面视觉稳定性。
如果面试官问“前端性能优化有哪些”,最好先讲“我会先看指标,再决定优化抓手”,这比纯列点更像真实工程实践。
一个总览图:从请求到可交互,哪里都可能慢
所以优化时也应该按链路思考,而不是只盯着“打包体积”。
一、网络与传输层优化
1. CDN 就近分发
- 静态资源放 CDN,缩短传输距离,降低跨地域访问延迟。
- 热点资源由边缘节点缓存,源站压力更低。
- 对图片、字体、JS/CSS 这类静态资源收益最明显。
常见追问:
- 为什么 CDN 能提升性能? 因为核心不是“带宽更大”,而是“用户到资源的物理距离更近,命中缓存的概率更高”。
2. 合理使用缓存
缓存通常分两层:
| 类型 | 典型响应头 | 作用 |
|---|---|---|
| 强缓存 | Cache-Control: max-age=31536000, immutable | 在有效期内直接使用本地副本,不发请求 |
| 协商缓存 | ETag / If-None-Match、Last-Modified / If-Modified-Since | 资源可能变化时,用较低成本确认是否更新 |
实践建议:
- 带 hash 的静态资源走长期强缓存。
- HTML 通常不做长期强缓存,因为它需要引用最新资源地址。
- 接口缓存要根据业务时效性设计,不能一刀切。
3. 启用压缩
- 文本资源优先开启 Brotli,其次是 Gzip。
- HTML、CSS、JS、JSON、SVG 都适合压缩。
- 图片和视频这类已经压缩过的二进制资源,重复压缩收益通常不大。
4. 降低连接建立成本
可用的资源提示:
<link rel="dns-prefetch" href="//static.example.com" />
<link rel="preconnect" href="https://static.example.com" crossorigin />
dns-prefetch:提前做 DNS 解析。preconnect:提前建立 DNS、TCP、TLS 连接,适合确定会访问的关键域名。
5. 减少请求数量,但不要机械合并
过去常讲“合并请求越少越好”,但在 HTTP/2/HTTP/3 下要更谨慎:
- 小文件过多会有请求管理成本。
- 但无脑合并成超大 bundle,也会拖慢首屏和缓存复用。
- 现代实践更强调:按业务边界拆分,让当前页面只下载当前需要的代码。
二、资源加载层优化
1. 代码分割(Code Splitting)
目标:减少首屏必须下载和执行的 JS。
常见拆分维度:
- 路由级拆分
- 组件级异步加载
- 第三方库按需引入
- 大型编辑器、图表库延迟加载
const ChartPanel = React.lazy(() => import('./ChartPanel'))
核心收益:
- 降低首屏 bundle 体积
- 缩短下载、解析、执行时间
- 提高缓存复用率
2. Tree Shaking 与依赖治理
- 移除未使用代码,避免“引了但没用”。
- 避免引入过重依赖,例如日期库、富文本库、图表库、工具库的全量版本。
- 优先选择支持 ESM、按需引入、sideEffects 标注清晰的库。
典型表达:
lodash全量引入往往不如按函数引入。moment往往比dayjs、date-fns更重。
3. 懒加载非关键资源
常见对象:
- 首屏以下图片
- 弹窗、抽屉、编辑器
- 评论区、推荐区、埋点脚本
- 地图、图表、客服 SDK
图片原生懒加载:
<img src="/images/demo.webp" loading="lazy" alt="示例图片" />
4. 预加载关键资源
区分两个概念:
| 技术 | 含义 | 适用场景 |
|---|---|---|
| preload | 当前页面马上会用到,优先加载 | 首屏字体、关键图片、关键脚本、关键 CSS |
| prefetch | 未来可能会用到,空闲时预取 | 下一跳页面资源、低优先级模块 |
示例:
<link rel="preload" href="/fonts/app.woff2" as="font" type="font/woff2" crossorigin />
<link rel="prefetch" href="/assets/settings.chunk.js" />
preload 不是越多越好。它会抢占带宽,如果把非关键资源也都设成高优先级,反而可能拖慢真正的关键资源。
5. 脚本加载策略
<script src="/app.js" defer></script>
<script src="/analytics.js" async></script>
defer:下载不阻塞 HTML 解析,等 DOM 解析完成后按顺序执行,适合主业务脚本。async:下载不阻塞解析,但下载完成就立即执行,顺序不保证,适合独立脚本。
三、页面渲染层优化
1. 缩短关键渲染路径
浏览器首屏渲染大致要经过:
- 解析 HTML 构建 DOM
- 解析 CSS 构建 CSSOM
- 合成 Render Tree
- Layout
- Paint
- Composite
所以常见优化手段包括:
- 减少阻塞渲染的 CSS/JS
- 关键 CSS 优先
- 非关键资源延迟
- 减少主线程长任务
2. 关键 CSS(Critical CSS)
把首屏必须的样式优先提供,避免“页面结构到了,但样式没到”。
<style>
.hero {
min-height: 320px;
padding: 24px;
}
.title {
font-size: 32px;
font-weight: 700;
}
</style>
注意:
- 只内联首屏必须样式。
- 非关键 CSS 继续走外链,保证缓存粒度。
- 内联过多会增大 HTML 体积,不是越多越好。
3. 降低回流(Layout)和重绘(Paint)
先分清三个概念:
| 阶段 | 含义 | 成本 |
|---|---|---|
| Layout | 计算元素几何位置和尺寸 | 高 |
| Paint | 把像素绘制出来 | 中到高 |
| Composite | 图层合成 | 相对低 |
实践建议:
- 动画优先改
transform、opacity - 避免频繁修改
width、height、top、left - 批量读写 DOM,避免布局抖动(layout thrashing)
- 减少大面积阴影、滤镜、模糊效果
4. 控制 DOM 规模和层级
- DOM 节点过多会提高样式计算、布局、绘制成本。
- DOM 层级过深会增加渲染和维护复杂度。
- 长列表、树结构、表格类场景要特别关注节点数量。
典型方案:
- 分页
- 虚拟列表
- 按需展开
- 折叠非关键区域
相关专题可以继续看本站已有文档:虚拟列表原理。
5. 减少布局偏移(CLS)
CLS 高通常不是“慢”,而是“页面乱跳”。
常见原因:
- 图片、广告、iframe 没有预留尺寸
- 字体切换导致文字重新排版
- 异步插入内容把已有内容顶下去
- 骨架屏和真实内容尺寸不一致
优化建议:
- 给图片和媒体元素明确宽高或比例盒子
- 字体使用
font-display - 异步内容预留占位空间
- 骨架屏尽量贴近真实布局
四、静态资源优化
1. 图片优化
图片通常是页面里最重的资源类型,优化优先级很高。
核心策略:
- 选择合适格式:照片优先 WebP / AVIF,图标优先 SVG
- 控制分辨率,避免“大图小用”
- 生成响应式图片
- 首屏图重点关注 LCP
- 非首屏图懒加载
响应式图片示例:
<img
src="/images/card-800.webp"
srcset="/images/card-400.webp 400w, /images/card-800.webp 800w, /images/card-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="商品图片"
/>
更完整的话题可看:图片压缩与优化全指南。
2. 字体优化
字体很容易被忽视,但它既影响首屏,也影响 CLS。
建议:
- 优先用
woff2 - 子集化字体,只保留需要的字符集
- 首屏关键字体可以
preload - 用
font-display: swap或按场景选择策略
@font-face {
font-family: 'AppFont';
src: url('/fonts/app.woff2') format('woff2');
font-display: swap;
}
3. CSS 优化
CSS 优化主要有两类:
- 交付性能:减小体积、减少阻塞、合理拆分。
- 运行时性能:减少样式重算、重排、重绘。
关键点:
- 移除无用 CSS
- 避免
@import - 按页面或组件拆分样式
- 减少高成本视觉效果
- 长内容场景可评估
content-visibility: auto
五、JavaScript 执行层优化
1. 减少主线程长任务
即使资源已经下载完成,也可能因为 JS 执行太重而导致首屏晚、交互卡。
常见重任务来源:
- 大型 bundle 初始化
- JSON 大对象解析
- 同步计算过多
- 一次性渲染大量节点
- 第三方 SDK 初始化太重
建议:
- 大任务拆小
- 延迟非关键逻辑
- 避免首屏做不必要计算
- 将重计算迁移到 Web Worker
2. 防抖和节流
适用场景:
- 输入搜索
- 滚动监听
- 窗口 resize
- 鼠标移动
核心价值不是“语法技巧”,而是降低高频事件触发的计算次数。
3. Web Worker
如果计算本身无法避免,就把它挪出主线程。
适用场景:
- 大数据排序、筛选、聚合
- 富文本转换
- 图像处理
- 加密解密
- 大文件切片
const worker = new Worker(new URL('./worker.js', import.meta.url))
worker.postMessage({ type: 'calculate', payload: largeData })
worker.onmessage = (event) => {
console.log('worker result', event.data)
}
4. 避免内存泄漏
性能不只是“快不快”,还包括“稳不稳”。
常见内存泄漏来源:
- 未移除的事件监听
- 未清理的定时器
- 闭包长期持有大对象
- 脱离文档树但仍被引用的 DOM
- 缓存无限增长
如果页面“越用越卡”,往往要查内存而不是只查网络。
六、框架层优化(React / Vue / 通用组件化场景)
1. 减少无效渲染
通用原则:
- 状态尽量局部化,不要一点小状态导致整页更新。
- 组件边界清晰,让更新范围尽量小。
- 大列表、大表格、大表单避免全量重渲染。
2. 列表渲染优化
常见手段:
- 稳定的
key - 分页
- 虚拟列表
- 惰性挂载不可见区域
3. 延迟非关键组件挂载
例如:
- 不在首屏的 Tab 面板
- 很晚才打开的弹窗
- 后台图表分析模块
- 编辑器、代码高亮器
4. 谨慎做“缓存”
很多人会把性能优化等同于“缓存所有东西”,但缓存也有成本:
- 占内存
- 增复杂度
- 可能带来脏数据
- 可能导致难以定位的显示错误
所以要强调:缓存是用空间换时间,但必须证明值得换。
七、数据获取与接口层优化
1. 减少无效请求
- 合并重复请求
- 做请求去重
- 组件卸载时取消不再需要的请求
- 避免瀑布流请求链过深
2. 做好数据缓存
常见场景:
- 列表页返回再进入
- 用户信息、权限信息、字典数据
- 不频繁变化的配置数据
缓存层次可以是:
- 内存缓存
- 浏览器缓存
- Service Worker 缓存
- IndexedDB / localStorage(按场景评估)
3. 优化接口返回
- 只返回当前页面需要的字段
- 避免一次返回超大列表
- 分页、游标、增量加载
- 压缩响应体
前端性能问题不总是前端代码造成的,接口设计也经常是瓶颈。
八、首屏与白屏专项优化
这是最常被单独追问的部分。
高收益手段通常包括:
- 缩短 TTFB
- 使用 CDN
- 压缩和缓存静态资源
- 关键 CSS 优先
defer业务脚本,减少阻塞- 首屏代码分割
- 骨架屏
- 图片优化和懒加载
- SSR / SSG / 预渲染
- 减少第三方脚本干扰
相关专题可以继续看:白屏优化。
先按根因分组,再落到对应优化手段。
九、性能监控与验证
1. 先测量,再优化
不要靠“感觉快了”。
常见工具:
- Chrome DevTools Performance
- Lighthouse
- WebPageTest
- Coverage
- Performance 面板中的长任务、Layout、Paint 分析
2. 线上采集核心指标
可以通过 PerformanceObserver 采集部分指标:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime)
}
})
observer.observe({ type: 'largest-contentful-paint', buffered: true })
说明:
- FCP、LCP、CLS、INP 这类指标更适合进入前端监控平台长期观察。
- 最好分端、分地区、分网络、分页面类型统计。
3. 建立性能预算
常见预算示例:
- 首屏 JS 不超过 200 KB gzip
- LCP 目标小于 2.5s
- CLS 小于 0.1
- 单个页面第三方脚本数量受控
没有预算,性能优化通常很难持续。
十、推荐的优化顺序
这是面试里很加分的一段,因为它体现的是工程优先级。
- 先看指标:慢的是加载、交互,还是稳定性?
- 先抓大头:图片、首屏 JS、TTFB、第三方脚本、长任务。
- 先做高 ROI 项:压缩、缓存、拆包、懒加载、图片格式优化。
- 再做专项优化:虚拟列表、Worker、SSR、骨架屏、字体治理。
- 最后做持续治理:监控、预算、CI 审计、依赖治理。
一个很实用的表达方式:
我一般不会一上来就做微优化,而是先确认瓶颈在网络、资源、渲染还是运行时,再优先处理影响面最大、收益最稳定的部分。
典型题 & 标准答法
Q1:前端性能优化有哪些?你怎么分类回答?
答法建议:
- 我会按链路回答,而不是按零散技巧回答。
- 第一类是网络与缓存,比如 CDN、缓存、压缩、预连接。
- 第二类是资源加载,比如代码分割、懒加载、图片优化、字体优化、
preload。 - 第三类是渲染优化,比如关键 CSS、减少阻塞 JS、减少回流重绘、控制 DOM 数量。
- 第四类是运行时优化,比如防抖节流、虚拟列表、Web Worker、减少无效渲染。
- 第五类是工程治理,比如体积分析、性能预算、线上监控。
Q2:为什么说减少首屏 JS 体积往往收益很高?
因为 JS 不只是“下载成本”,还有解析、编译、执行成本。尤其在中低端设备上,执行成本常常比下载更痛。首屏 JS 太大,会拖慢 FCP、LCP,也会拉高 TBT 和 INP。
Q3:async、defer、preload、prefetch 分别适合什么场景?
答法要点:
async:独立脚本,执行顺序不重要。defer:主业务脚本,保证顺序,避免阻塞 HTML 解析。preload:当前页面马上要用的关键资源。prefetch:未来大概率会用,但不是当前关键路径资源。
Q4:只做打包优化够吗?
不够。前端性能至少还涉及:
- 网络连接和 TTFB
- 图片和字体
- 渲染链路
- 主线程长任务
- 运行时内存
- 第三方脚本
- 线上监控
只盯打包体积,很容易漏掉真正的大头。
Q5:性能优化应该怎么验证?
答法要点:
- 本地用 Lighthouse、DevTools、Coverage、Performance 面板定位。
- 线上采集 Core Web Vitals 和长任务指标。
- 做 A/B 对比,确认优化前后指标变化。
- 关注不同设备、不同网络环境下的结果,而不是只看自己的开发机。
常见误区
-
误区 1:把所有资源都 preload。 结果可能是带宽竞争,关键资源反而更慢。
-
误区 2:只看包大小,不看执行时间。 JS 慢很多时候不是下载慢,而是解析执行慢。
-
误区 3:只做实验室数据,不看真实用户数据。 本地跑得快,不代表弱网和低端机也快。
-
误区 4:把缓存当万能药。 缓存会引入失效、容量、脏数据问题。
-
误区 5:为了性能牺牲可维护性。 某些“极限优化”会让工程复杂度暴涨,长期未必划算。
速记要点(适合背诵)
可以把前端性能优化背成这 8 组关键词:
- 连得快:DNS、TCP、TLS、CDN、预连接
- 传得少:压缩、缓存、精简资源
- 下得准:代码分割、懒加载、按需加载
- 来得早:关键 CSS、
preload、首屏优先 - 渲得轻:少 DOM、少回流、少重绘
- 算得省:防抖节流、Worker、减少长任务
- 更稳定:预留尺寸、控制 CLS、清理内存
- 可治理:预算、监控、分析、持续优化
如果面试官继续追问,你可以沿着这 8 组逐步展开。