性能为什么直接影响留存
页面每多等一秒,就有一批用户在这一秒里关掉页面走人。性能不是锦上添花的体验问题,而是和转化率、留存率直接挂钩的业务指标。优化前端性能的第一原则是:先测量,再动手。凭感觉"感觉有点慢"去优化,往往改了一堆无关紧要的地方,真正的瓶颈却纹丝不动。
性能优化的顺序永远是:量化现状 → 定位最大瓶颈 → 针对性优化 → 再量化验证,而不是上来就堆技巧。
- 加载耗时下载 HTML、CSS、JS、图片等资源的时间,体积与请求数决定。
- 渲染耗时浏览器解析、布局、绘制到首次可见的时间。
- 交互耗时点击到页面响应的时间,由主线程是否被长任务阻塞决定。
Core Web Vitals:先盯住三个核心指标
Google 把与用户体感最相关的指标归为 Core Web Vitals,只需先盯这三个:
| 指标 | 含义 | 达标参考 |
|---|---|---|
| LCP | 最大内容绘制,主视觉多久出现 | 2.5 秒以内 |
| INP | 交互到下一次绘制的响应速度 | 200 毫秒以内 |
| CLS | 布局累积偏移,页面是否乱跳 | 0.1 以内 |
LCP 通常由首屏大图或大字块决定,所以首屏关键图片的加载优先级是优化重点;INP 慢往往是因为主线程被一大段同步 JS 占住,用户点了却没反应;CLS 则多来自图片没有预设宽高、字体加载后撑大布局,给媒体元素写死尺寸就能立竿见影。
用 Lighthouse 找到瓶颈
Lighthouse 是浏览器自带的性能审计工具,跑一次会给出 0 到 100 的分数,并列出具体问题:未压缩的图片、阻塞渲染的脚本、过大的 JS 包、未使用的 CSS。它把"慢"翻译成可执行的清单。真实用户的体验还可以用 Web Vitals 库采集,区分实验室数据与真实场域数据——Lighthouse 测的是模拟环境,真实网络下的数据才更接近用户。
// 浏览器里采集真实场域指标
import { onLCP, onINP, onCLS } from "web-vitals";
onLCP((m) => console.log("LCP", m.value));
onINP((m) => console.log("INP", m.value));
onCLS((m) => console.log("CLS", m.value));
减少请求体积:压缩与格式
体积是最直接的杠杆。图片方面,能不用 PNG 大图就不用,照片类内容用 WebP 或 AVIF,通常能比 JPEG 小两三成;首屏之外的图片才懒加载。代码方面,构建工具默认开启的压缩会删掉空格与注释,进一步还可以移除 console 与未使用代码。开启服务器的 gzip 或 Brotli 压缩,文本资源再砍掉一半以上,这一步几乎零成本却收益巨大。
加载顺序:预加载、异步与懒加载
浏览器默认按 HTML 顺序加载资源,阻塞渲染的 CSS 与放错位置的大脚本都会拖慢首屏。常用手段有三种:defer 让脚本等 HTML 解析完再执行、不阻塞渲染;preload 提前声明首屏关键资源;loading="lazy" 让首屏外的图片进入视口时才加载。
<!-- 不阻塞渲染的脚本,按顺序执行 -->
<script src="/app.js" defer></script>
<!-- 提前加载首屏关键字体与大图 -->
<link rel="preload" href="/hero.avif" as="image">
<!-- 非首屏图片懒加载 -->
<img src="/cover.avif" width="800" height="600" loading="lazy">
注意写死 width 和 height,浏览器才能在图片到达前就预留位置,避免布局跳动拖累 CLS。
代码拆分与缓存策略
把整个应用打成一个 JS 文件,用户为了看首页也要下载全部代码。代码拆分按路由或组件把大包切成小块,配合动态 import,访问哪个页面才下载哪个页面的代码。第三方依赖体积稳定但更新少,单独打成 vendor 包,能和业务代码分开缓存。
缓存策略上,文件名带内容哈希是关键:app.a1b2c3.js 这样的命名让浏览器可以对不变的文件长期强缓存,而 HTML 本身设置短缓存或不缓存。这样发版后用户只下载变化过的那一小块,而不是每次都重下全部静态资源。
小结与优化清单
性能优化没有终局,但抓住主线就不会跑偏:先测三个核心指标,用 Lighthouse 找到最大的那块"短板",再从体积、加载顺序、代码拆分三个方向下手。
- 先测后改,用真实场域数据验证优化效果。
- 图片用新格式并压缩,非首屏懒加载、写死尺寸。
- 开启文本压缩,脚本加 defer,关键资源 preload。
- 按路由拆包,静态资源带内容哈希做长缓存。