“性能优化”不是背几条口诀,而是理解浏览器把 HTML / CSS / JS / 图片 / 字体 / 网络请求 变成“可见、可交互页面”的全过程,然后在每个阶段减少阻塞、减少浪费、减少抖动。
前端面试里,性能优化几乎是必考题。很多候选人会回答“压缩图片、懒加载、减少请求”,这些当然没错,但真正高质量的回答应该覆盖:
- 指标是什么
- 瓶颈在哪一层
- 如何定位
- 如何系统优化
- 如何避免回归
本文按这个思路,把前端性能优化拆成一套完整的知识体系。
一、先明确:前端性能优化到底在优化什么?
前端性能不是单一指标,而是几个维度共同决定的:
- 加载速度:页面多久能看到主要内容
- 可交互速度:用户点击后多久有响应
- 视觉稳定性:页面是否乱跳
- 运行流畅度:滚动、动画、输入是否卡顿
- 资源效率:是否下载了太多不必要的内容
所以性能优化不能只盯着“首屏时间”,而要同时看 加载、渲染、交互、稳定性、运行时。
二、最重要的一组指标:Core Web Vitals
Google 目前最常用的体验指标是 Core Web Vitals:
| 指标 | 含义 | 理想阈值 | 优化重点 |
|---|---|---|---|
| LCP | Largest Contentful Paint,最大内容绘制 | ≤ 2.5s | 首屏主内容加载 |
| INP | Interaction to Next Paint,交互到下一次绘制 | ≤ 200ms | 点击、输入、交互响应 |
| CLS | Cumulative Layout Shift,累计布局偏移 | ≤ 0.1 | 视觉稳定性 |
LCP 示意图
LCP 衡量的是:用户看到页面主要内容需要多久。它通常对应首屏的大图、主标题、大段正文、Hero 区块。
如果 LCP 很差,说明问题往往出在:
- HTML 返回太慢
- CSS/JS 阻塞渲染
- 首屏大图太大
- 字体加载阻塞文本显示
- 首屏资源没有被优先加载
CLS 示意图
CLS 衡量的是:页面加载过程中内容有没有突然跳动。
最常见的 CLS 来源:
- 图片没写宽高
- 广告、弹窗、异步组件后插
- Web Font 切换导致文字重排
- 上方区域后续才插入内容
INP 的本质
INP 关注的是:用户点击之后,界面多久真正做出响应。
INP 差,说明问题通常不在网络,而在:
- 主线程被长任务阻塞
- JavaScript 执行过重
- React/Vue 组件更新链太长
- 事件回调里做了太多同步计算
三、性能优化的完整思路:分层解决
比较成熟的性能优化思路可以分成 5 层:
- 网络与传输层
- 资源加载层
- 渲染层
- JavaScript 执行层
- 监控与治理层
你在面试里如果能按这 5 层展开,回答会非常完整。
四、网络与传输层优化
很多性能问题在浏览器开始渲染之前就已经发生了。
4.1 减少 RTT 和资源获取延迟
常见做法:
- 使用 CDN
- 开启 HTTP/2 / HTTP/3
- 合理利用浏览器缓存和边缘缓存
- 减少重定向
- 使用更快的 DNS 和连接复用
对于第三方域名,可以提前做连接预热:
<link rel="dns-prefetch" href="//cdn.example.com"><link rel="preconnect" href="https://cdn.example.com" crossorigin>这类优化对首屏资源尤其有效。
4.2 服务端响应时间要控制住
如果 TTFB 很慢,后续所有优化都会被拖累。
典型做法:
- SSR / SSG / ISR 提前产出页面
- 接口层缓存
- 数据库查询优化
- 避免首页接口瀑布流依赖
一句话总结:前端性能优化不只在前端,后端响应慢一样会把首屏打穿。
五、资源加载层优化:先减少,再延迟,再优先级控制
前端资源优化的核心策略其实就三句话:
- 少下载
- 晚下载
- 先下载最重要的
5.1 减少包体积
这是最基础、也是收益很高的一类优化:
- Tree Shaking
- 代码分割(Code Splitting)
- 动态导入(Dynamic Import)
- 删除未使用依赖
- 使用轻量库替代重型库
- 开启压缩(gzip / brotli)
例如:
const Chart = await import('./Chart.js')把不影响首屏的功能拆出去,通常比“继续微调首屏 JS”更有效。
5.2 图片优化
图片几乎是最常见的性能杀手。
最佳实践包括:
- 优先使用 WebP / AVIF
- 针对不同尺寸输出响应式图片
- 首屏图优先加载,非首屏图懒加载
- 控制图片展示尺寸,不要 4000px 图片显示成 400px
- 对图片设置明确
width/height
示例:
<img src="/banner.webp" width="1200" height="630" loading="lazy" alt="banner">如果是首屏 Hero 图,不要盲目 lazy,反而应该优先加载。
5.3 字体优化
字体非常容易导致首屏变慢和 CLS。
常见策略:
- 只加载需要的字重
- 使用字体子集(subset)
- 本地缓存字体
- 使用
font-display: swap - 首屏必要字体用
preload
<link rel="preload" href="/fonts/inter-subset.woff2" as="font" type="font/woff2" crossorigin>5.4 脚本加载策略
脚本标签如果处理不好,最容易阻塞 HTML 解析。
一般原则:
- 非关键脚本用
defer - 完全独立脚本可考虑
async - 第三方脚本尽量延后
- 避免把大量业务逻辑塞进首屏同步执行
<script src="/main.js" defer></script>5.5 CSS 优化
CSS 会阻塞渲染,因此首屏 CSS 的处理很关键。
常见做法:
- 提取 Critical CSS
- 删除未使用 CSS
- 拆分页面级样式
- 避免巨型样式包首屏全量加载
六、渲染层优化:关键渲染路径越短越好
浏览器把页面渲染出来,大致要经历:
- 解析 HTML 构建 DOM
- 解析 CSS 构建 CSSOM
- 合成 Render Tree
- Layout(布局)
- Paint(绘制)
- Composite(合成)
性能优化里一个非常核心的概念就是:
缩短关键渲染路径(Critical Rendering Path)
6.1 减少阻塞首屏渲染的资源
首屏真正必要的只有三类:
- 当前页面结构
- 当前页面样式
- 当前页面必须立即执行的逻辑
其他东西都应该延后。
例如:
- 评论系统延后
- 埋点 SDK 延后
- 聊天插件延后
- 推荐模块懒加载
- 首屏以下图片懒加载
6.2 降低重排(Reflow)和重绘(Repaint)成本
高频修改布局属性会带来很大开销。
应尽量避免在动画中频繁修改:
widthheighttopleftmargin
更推荐使用:
transformopacity
原因是这类属性通常可以在合成阶段完成,性能更稳定。
6.3 避免布局抖动(Layout Thrashing)
最典型的反模式是:
- 先写样式
- 再读布局
- 再写样式
- 再读布局
例如频繁读取:
offsetWidthoffsetHeightgetBoundingClientRect()
这会强制浏览器立即执行布局计算,导致卡顿。
正确思路是:批量读、批量写。
6.4 合理使用新特性
一些现代 CSS / 浏览器能力对性能优化很有帮助:
content-visibility: autocontain- 原生懒加载
loading="lazy"decoding="async"
例如:
.section-below-the-fold { content-visibility: auto; contain-intrinsic-size: 800px;}这对超长页面尤其有效。
七、JavaScript 执行层优化:别把主线程堵死
页面卡顿,很多时候不是因为“资源没下来”,而是因为“JS 执行太重”。
7.1 拆分长任务
如果一个任务执行超过 50ms,就可能影响交互响应。
典型场景:
- 大量 JSON 解析
- 大列表过滤 / 排序
- 富文本计算
- 大量同步循环
- 一次性渲染巨大组件树
解决方法:
- 分批执行
- 调度到空闲时机
- 使用 Web Worker
7.2 避免一次渲染太多 DOM
长列表是最典型的性能问题来源。
最佳实践:
- 列表虚拟化(Virtual List)
- 分页
- 无限滚动 + 视口渲染
不要一次把几千个节点全部塞进 DOM。
7.3 减少无意义的重复渲染
无论 React 还是 Vue,运行时性能都很容易被“无效更新”拖垮。
常见做法:
- 细化组件边界
- 避免父组件频繁触发全树更新
- 缓存计算结果
- 合理使用 memo / computed / watch
- 避免深层响应式对象被频繁整体替换
7.4 让高优先级交互先执行
用户输入、点击、滚动这些交互优先级最高。
不要在输入事件里同步执行大量逻辑,例如:
- 实时做复杂校验
- 实时请求接口
- 实时重排整个组件树
更好的做法:
- 节流(throttle)
- 防抖(debounce)
- 分优先级调度
- 延迟非关键任务
八、CLS 专项优化:页面别跳
CLS 是很多项目最容易忽略,但用户体感最差的一类问题。
8.1 图片、视频、iframe 必须预留尺寸
<img src="/cover.webp" width="800" height="450" alt="cover">这是最经典的防 CLS 手段。
8.2 异步内容不要把已渲染内容顶下去
例如:
- 广告位
- 推荐组件
- 弹窗容器
- 登录提示条
应该先预留占位区域,而不是加载后突然插入。
8.3 字体切换要控制
如果自定义字体加载前后字宽差异很大,也会引发布局偏移。
常见策略:
font-display: swap- 选用度量接近的后备字体
- 关键字体预加载
九、缓存策略:让二次访问飞起来
第一次访问优化固然重要,但真实业务里二次访问往往更多。
9.1 强缓存与协商缓存
常见头部:
Cache-Control: public, max-age=31536000, immutableETag: "abc123"适合长期缓存的资源:
- 构建产物 JS/CSS
- 带 hash 的静态资源
- 图片字体
9.2 Service Worker / 离线缓存
对于 PWA 或需要离线体验的场景,可以进一步使用:
- 预缓存
- 运行时缓存
- 离线回退页
但要注意:缓存体系越强,失效策略越要设计好。
十、第三方脚本治理:很多项目的隐藏大坑
很多性能差的站点,并不是业务代码太烂,而是塞了太多第三方:
- 埋点 SDK
- A/B Test
- 广告脚本
- 在线客服
- 地图 SDK
- 评价插件
这些脚本的共同问题是:
- 体积大
- 时机不可控
- 执行开销高
- 失败时还可能拖垮页面
治理策略:
- 非首屏必需的全部延后
- 控制第三方脚本数量
- 做性能预算
- 定期复盘哪些脚本已经没有业务价值
十一、监控与度量:不测量,就没有优化
真正成熟的性能优化,不是“靠感觉变快了”,而是可量化。
11.1 开发阶段工具
- Chrome DevTools Performance
- Lighthouse
- Network 面板
- Coverage 面板
- Performance Insights
11.2 线上监控
建议同时做两类监控:
- Synthetic Monitoring(实验室数据)
- RUM(真实用户监控)
实验室数据适合回归测试;RUM 更适合看真实网络、真实设备、真实地区分布。
11.3 建立性能预算
例如可以给首页定规则:
- 首屏 JS 不超过 200KB
- 首屏图片不超过 300KB
- LCP ≤ 2.5s
- INP ≤ 200ms
- CLS ≤ 0.1
这样性能优化就从“口号”变成“工程约束”。
十二、面试里怎么回答“前端性能优化最佳实践”?
如果是面试场景,我建议用下面这个结构:
第一层:先讲目标指标
我会先关注 LCP、INP、CLS,因为它们分别反映首屏加载、交互响应和视觉稳定性。
第二层:按层拆解优化
- 网络层:CDN、缓存、减少重定向、preconnect
- 资源层:压缩、拆包、懒加载、图片与字体优化
- 渲染层:缩短关键渲染路径、减少重排重绘、优先首屏
- JS 层:拆分长任务、减少主线程阻塞、虚拟列表、避免无效渲染
- 监控层:Lighthouse + DevTools + RUM + 性能预算
第三层:强调“定位能力”
不是一上来乱优化,而是先通过:
- Lighthouse
- Chrome Performance
- Network Waterfall
- Web Vitals
定位瓶颈,再针对性优化。
这会让你的回答从“知道几个手段”升级成“有系统方法论”。
十三、性能优化不是一招鲜,而是一整套工程能力
很多人会问:有没有最重要的一条最佳实践?
如果只能给一句话,我会说:
让最重要的内容最早到达、最早渲染、最早可交互,同时把不重要的内容延后。
这句话展开之后,其实就是整套性能优化体系:
- 重要资源优先
- 非关键资源延后
- 减少传输体积
- 减少主线程阻塞
- 减少布局抖动
- 用数据持续监控
总结
前端性能优化的最佳实践,可以浓缩成下面这张清单:
- 用 LCP / INP / CLS 定义目标
- 用 DevTools / Lighthouse / RUM 找瓶颈
- 压缩、拆分、懒加载 JS/CSS/图片/字体
- 缩短关键渲染路径,优先首屏资源
- 减少重排、重绘和主线程长任务
- 控制第三方脚本
- 用缓存和 CDN 提升复访性能
- 建立性能预算,防止优化回退
如果你把这套逻辑真的掌握了,那么“前端性能优化”这道面试题,你的回答就已经不只是“会几个技巧”,而是具备系统性工程思维。