前端开发
前端性能优化实战:从首屏渲染到交互流畅
<>
性能优化的两个维度
用户感知的性能分两段:加载性能(页面多久能看)与运行性能(操作是否跟手)。前者由关键渲染路径决定,后者由渲染/布局成本决定。先量化再优化,避免拍脑袋。
关键渲染路径(CRP)
HTML → CSSOM → Render Tree → Layout → Paint → Composite
↓ ↓
阻塞渲染 阻塞渲染
浏览器拿到 HTML 后要构建 DOM 和 CSSOM,两者齐备才首次渲染。优化要点:
- CSS 放 head、JS 放 body 底部:script 会阻塞解析,能用
defer就不要同步加载 - 首屏关键 CSS 内联:把首屏样式内联,其余异步加载
- 图片:
loading="lazy"延迟加载、srcset按屏宽选图、WebP 格式
<script defer src="app.js"></script>
<link rel="preload" href="hero.webp" as="image">
<img src="hero.webp" alt="" loading="lazy">
资源加载策略
# 构建产物体积控制
1. 代码分割:路由级按需加载(React.lazy)
2. Tree Shaking:只打包用到的导出
3. 压缩:gzip/brotli(通常能省 60%+ 传输体积)
# HTTP 缓存
- 静态资源: Cache-Control: public, max-age=31536000, immutable
- HTML: Cache-Control: no-cache(保证更新可达)
- 文件名带 hash:内容变化则 URL 变化,缓存自动失效
运行时性能:让操作不卡顿
浏览器一帧只有 16.6ms,脚本、布局、绘制超时就掉帧:
- 避免布局抖动(Layout Thrashing):不要在循环里交替读
offsetWidth再改样式,先批量读、再批量写 - 动画只动 transform/opacity:它们走合成层,不触发布局和绘制
- 事件节流防抖:
scroll、resize、input高频事件必须限频 - 长任务拆分:超过 50ms 的任务用
requestIdleCallback或setTimeout分片
// 防抖:输入停止后才执行
function debounce(fn, wait = 300) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
}
function throttle(fn, interval = 100) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= interval) { last = now; fn(...args); }
};
}
长列表:虚拟滚动
渲染 1 万条 DOM 必然卡死。虚拟滚动只渲染视口内的条目,用 padding 撑起总高度:
// 思路:只渲染可见区 ~30 条
const visible = items.slice(start, end);
return (
<div style={{ height: totalHeight, overflow: "auto" }}>
<div style={{ transform: translateY(start * rowHeight) }}>
{visible.map(renderItem)}
</div>
</div>
);
量化验证:别凭感觉
# Lighthouse 五个核心指标
LCP 首次内容渲染最大元素 < 2.5s
INP 交互到下一次绘制 < 200ms
CLS 累积布局偏移 < 0.1
FCP 首次内容绘制 < 1.8s
TTFB 服务器响应时间 < 800ms
每次改动后跑一次 Lighthouse 与性能录制,对比指标变化,让优化可度量、可回归。
性能优化不是一次性手术,而是持续的健康管理 -- 指标先行,量入为出。