本节摘要:性能优化不是"上线前突击",而是一套可执行的工程动作。本节把第 3.3 的性能维度落成四个具体手段:代码分割(按需加载 JS)、懒加载(延迟加载资源与组件)、缓存(减少重复请求)、图片优化(体积与格式),并按"收益 / 成本"排出优化优先级,给出从测量到优化的完整闭环。核心结论:先测量再优化,优化跟着"最大的瓶颈"走。
阅读完本节,你应当能够:
一个常见误区:以为"体积大 = 慢,体积小 = 快"。实际上加载时间是"下载 + 解析执行"的复合结果,还受网络、缓存、关键资源顺序影响。你砍了包体积但没优化"首屏真正需要的资源",用户看到的首屏照样慢。反过来说,一个体积不小但"关键路径优化得好"的包,首屏可能很快。
所以性能优化的第一步永远是测量——找出"首屏时间到底花在哪"(下载?解析?请求?渲染?),再对症下药。Lighthouse 给你分数,DevTools Performance 面板告诉你时间花在哪,Network 面板告诉你请求了多少东西。没有测量的优化是"拍脑袋减肥",可能减错了地方。
💡 关键直觉:性能优化像治理堵车——先找到"哪个路口堵",再修那个路口。上来就扩路(砍体积)但堵点在红绿灯(关键资源顺序),白干。
目标:浏览器首屏只下载当前页面需要的代码,而不是整个应用。三种粒度:
React.lazy + Suspense,Vue 用异步组件 + 路由懒加载,Angular 用路由模块懒加载。懒加载比代码分割更广义——不止代码,图片、组件、模块都能懒加载。
loading="lazy" 原生属性或 IntersectionObserver 方案),长列表、长页面收益最大。Cache-Control 与 ETag,未变化的资源不重复下载。打包时给文件名加内容 hash(内容变文件名变),实现"永缓存 + 精准失效"。srcset/sizes 让浏览器按设备选图。循环执行:用 Lighthouse 定基线 → 用 Performance/Network 面板定位瓶颈 → 针对性优化 → 复测对比。优化没经过复测,等于没验证——很多"优化"实际上只是"感觉快了一点"。
| 优先级 | 手段 | 收益 | 成本 |
|---|---|---|---|
| 高 | 路由级代码分割 | 首屏 JS 显著减少 | 低(配置即用) |
| 高 | 图片懒加载 + 压缩 | 长页面与图片站收益大 | 低 |
| 中 | 缓存策略配置 | 二次访问体验提升 | 低 |
| 中 | 减少关键资源(移除未用库、Tree Shaking) | 体积下降 | 中 |
| 低 | 组件级分割 / 微优化 | 边际收益 | 中高 |
⚠️ 常见坑:优化顺序倒置——先花大力气做"组件级微优化",却连"路由懒加载"都没做。从高优先级(低垂果实)开始,让每一分优化成本都花在最值的地方。
React.memo:纯展示组件避免无谓重渲染。useMemo/useCallback:缓存计算与函数引用(注意依赖数组稳定)。useTransition):大更新不阻塞交互。keep-alive:缓存不销毁的组件,切回时免重建。性能优化要设"达标线"而不是"无限优化":先定一个明确的性能目标(如"Lighthouse 性能分 90+"或"移动端 FCP 2 秒内"),优化到达标即收手,把剩余精力留给业务。性能是"够用就好"的工程,不是"永不满足"的竞赛——过度优化的时间成本,会吃掉本该投入业务的资源。
优化不是一次性的:引入新依赖、新功能都可能拖慢性能。把性能监控接进 CI(构建后跑一次 Lighthouse)或在生产接入 RUM(真实用户监控),性能一旦退化就报警。让性能成为"持续守护的指标",而不是"上线前突击的考试"。
性能优化做到一定阶段,边际收益会递减,这时更需要"预算"思维:像管财务一样给关键指标设额度,超标即拦截。比如给首屏 JS 设 200KB 预算——每次新增依赖或功能,用构建产物分析工具检查是否超预算,超了就触发评审(这个库值得加吗?能按需引入吗?)。预算思维把"性能优化"从"事后补救"变成"事前把关":不是等慢了再优化,而是从一开始就不让"会拖慢的东西"进来。配合 3.2 的优先级表,预算解决"往哪挡"的问题,优先级解决"先优化什么"的问题——两者结合,性能治理就从"打地鼠"变成"设防线"。前端性能问题的根源多半是"不知不觉变胖",预算机制正是治这个"不知不觉"。
性能优化最大的敌人不是技术,是"无人负责"。技术团队日常被业务压着跑,"性能"永远排在"新功能"后面,直到线上出问题才被想起来。要打破这个循环,需要组织层面的安排:一是指定性能负责人——哪怕不是专职,也要有一个人对性能指标负责,定期出报告、推动优化;二是把性能写进开发流程——新功能上线前过一遍"会不会拖慢性能"的检查,作为评审项之一;三是用数据说话——建立核心页面性能基线(FCP/LCP/CLS),每次上线前后对比,退化即回归。这三件事都不复杂,难的是坚持。有了组织保障,性能优化才能从"个人觉悟"变成"系统行为"——这也是为什么同样做性能,有的团队越做越稳,有的团队总是"上次优化这次又慢"。技术是手段,负责才是机制。
性能指标(FCP、LCP、TTI)是客观的,但用户体验是主观的——两者之间有个微妙的落差,值得性能优化者理解。一个例子:页面 LCP 很快但点击按钮后没有即时反馈,用户会觉得"卡";反过来,页面加载不算最快,但每个交互都有即时反馈,用户反而觉得"顺"。这背后的原理是感知性能:人对"等待反馈"的容忍度极低,但对"整体加载"的耐心相对高。所以性能优化不只要优化"数字",还要优化"感知":点击后立即出现 loading 态、骨架屏让内容区域先占位、动画与进度条让等待变得可预期。几个零成本的做法:按钮点击即时置灰、图片先显示占位符、路由切换先出现"切换中"反馈。把"感知性能"纳入优化清单,你会发现有些"慢"根本不用等技术手段,加一个 loading 态就解决了——用户抱怨的往往不是慢,而是"没反应"。
性能优化最理想的状态,不是事后救火,而是把优化"内建"进日常开发习惯——这需要与框架特性结合形成约定。React 团队约定:组件默认不 memo,除非基准测试证明需要;列表渲染必须有稳定 key;大依赖必须懒加载。Vue 团队约定:巨型响应式对象用 shallowRef 隔离;v-for 必须配 key;模板里不写复杂表达式。Angular 团队约定:默认 OnPush 变更检测;大列表用 trackBy。这些约定不是"高深的优化技巧",而是"日常开发的口头禅"——它们把 80% 的常见性能问题消灭在写代码阶段,而不是等性能报告出来再追。落地方式很朴素:写进团队编码规范文档、进 Code Review 的检查项、在脚手架模板里默认配置好。当优化成为"默认正确"而不是"额外要求",性能就不再是某个人的责任,而成了团队的习惯——这是性能优化能做到的最深一层。
性能问题常常不是一次大滑坡,而是几十次小退化累积的——每次上线慢 0.1 秒,没人察觉,半年后慢成灾难。要治这个"温水煮青蛙",需要给性能也建立"回归防线",让它和 Bug 一样在 CI 被拦截。做法是性能预算自动化:把关键指标(首屏 JS 体积、LCP 预算)写进 CI 脚本,每次构建后自动测量,超预算直接失败并提示"哪个模块让体积涨了 X KB"。配合产物分析报告,团队能精确定位"这次是谁加了 50KB"。这样的自动化防线有几个好处:性能退化第一时间暴露(不用等用户投诉)、责任人明确(超预算的提交者自己处理)、优化收益可验证(达标后别人不会改回去)。性能回归防线的本质,是把第 3.7 节的"预算思维"从文档变成机制——预算不是贴在墙上的口号,而是拦在 CI 里的关卡。当性能退化和代码 Bug 一样"过不了门禁",性能健康才算有了制度保障。
单应用的质量与体验都理顺了,最后看"应用大了怎么办"——微前端与多框架共存。