· 6 min read
前端性能优化笔记
性能优化
研究表明,页面加载时间每增加一秒,用户流失率可能上升 7%。
1. 核心指标:Web Vitals
Google 提出的 Web Vitals 是衡量用户体验的核心指标:
1.1 LCP(Largest Contentful Paint)
最大内容绘制,衡量页面主要内容加载完成的时间。
// 使用 Web Vitals 库监控 LCP
import { onLCP } from 'web-vitals';
onLCP(({ value, entries }) => {
console.log(`LCP: ${value}ms`);
// 优化目标: LCP < 2.5s
});
1.2 FID(First Input Delay)
首次输入延迟,衡量用户首次交互的响应速度。
import { onFID } from 'web-vitals';
onFID(({ value }) => {
console.log(`FID: ${value}ms`);
// 优化目标: FID < 100ms
});
1.3 CLS(Cumulative Layout Shift)
累积布局偏移,衡量页面视觉稳定性。
import { onCLS } from 'web-vitals';
onCLS(({ value }) => {
console.log(`CLS: ${value}`);
// 优化目标: CLS < 0.1
});
p s: 使用 Chrome DevTools 的 Lighthouse 面板可以一键测试这些指标。
2. 资源优化策略
2.1 资源压缩与合并
// webpack 配置示例
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
},
},
}),
],
},
};
2.2 图片优化
// 使用 Next.js Image 组件
import Image from 'next/image';
function ProductImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
placeholder="blur"
blurDataURL="data:image/..." // 模糊占位图
loading="lazy"
quality={75}
width={400}
height={300}
/>
);
}
2.3 WebP 图片格式
<!-- 响应式图片使用 picture 标签 -->
<picture>
<source srcset="image.webp" type="image/webp" />
<source srcset="image.jpg" type="image/jpeg" />
<img src="image.jpg" alt="描述" />
</picture>
3. 缓存策略
3.1 浏览器缓存配置
// Next.js next.config.js 配置
module.exports = {
async headers() {
return [
{
source: '/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
source: '/:path*.json',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=3600, must-revalidate',
},
],
},
];
},
};
3.2 Service Worker 缓存
// sw.js
const CACHE_NAME = 'my-app-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles/main.css',
'/scripts/main.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// 缓存命中返回缓存,否则请求网络
return response || fetch(event.request);
})
);
});
4. 代码拆分与懒加载
4.1 路由级代码拆分
// React Router v6 + React.lazy
import { Routes, Route } from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
4.2 组件级懒加载
import { useState, lazy, Suspense } from 'react';
function ModalContainer() {
const [showModal, setShowModal] = useState(false);
// 动态导入大型组件
const HeavyModal = lazy(() => import('./HeavyModal'));
return (
<>
<button onClick={() => setShowModal(true)}>打开弹窗</button>
{showModal && (
<Suspense fallback={<ModalLoading />}>
<HeavyModal onClose={() => setShowModal(false)} />
</Suspense>
)}
</>
);
}
4.3 图片懒加载
// 使用 Intersection Observer 实现图片懒加载
function LazyImage({ src, alt }) {
const [isVisible, setIsVisible] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
});
});
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef}>
{isVisible && <img src={src} alt={alt} />}
</div>
);
}
5. 渲染优化
5.1 减少重排与重绘
// ❌ 避免:多次修改 DOM 触发多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';
// ✅ 推荐:使用 CSS 类一次性修改
element.classList.add('expanded');
// ✅ 推荐:使用 document fragment
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
ul.appendChild(fragment);
5.2 使用 CSS Transform 和 Opacity
/* ❌ 影响布局的属性 */
.element {
width: 100px;
height: 100px;
top: 50px;
}
/* ✅ 不影响布局的属性 */
.element {
transform: translateX(50px);
opacity: 0.5;
}
5.3 React 性能优化
import { memo, useMemo, useCallback } from 'react';
// 使用 memo 避免不必要的重渲染
const ListItem = memo(({ item, onClick }) => {
return <li onClick={() => onClick(item.id)}>{item.name}</li>;
});
// 使用 useMemo 缓存计算结果
function ExpensiveComponent({ data, filter }) {
const filteredData = useMemo(() => {
return data.filter(item => item.name.includes(filter));
}, [data, filter]);
return filteredData.map(item => <ListItem key={item.id} item={item} />);
}
// 使用 useCallback 缓存回调函数
function Parent() {
const handleClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
return <Child onClick={handleClick} />;
}
6. 网络优化
6.1 CDN 配置
// next.config.js 配置 CDN
module.exports = {
images: {
domains: ['cdn.example.com', 'images.unsplash.com'],
path: '/_next/image',
loader: 'default',
},
};
6.2 预连接和预加载
<!-- 预连接到关键域名 -->
<link rel="preconnect" href="https://cdn.example.com" />
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin />
<!-- 预取下一个路由 -->
<link rel="prefetch" href="/dashboard" />
6.3 异步加载第三方脚本
<!-- 方式 1: async -->
<script src="https://analytics.example.com/script.js" async></script>
<!-- 方式 2: defer -->
<script src="https://analytics.example.com/script.js" defer></script>
<!-- 方式 3: 动态加载 -->
<script>
const script = document.createElement('script');
script.src = 'https://analytics.example.com/script.js';
script.async = true;
document.head.appendChild(script);
</script>
7. 性能测试工具
7.1 Lighthouse
# 使用 Chrome CLI 运行 Lighthouse
lighthouse https://example.com \
--preset=desktop \
--view \
--output=json \
--output-path=./lighthouse-report.json
7.2 Web Vitals 实际指标
// 完整的性能监控实现
import { getCLS, getFID, getLCP } from 'web-vitals';
function sendToAnalytics({ name, value, id }) {
// 发送到分析服务
gtag('event', name, {
event_category: 'Web Vitals',
event_label: id,
value: Math.round(name === 'CLS' ? value * 1000 : value),
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
8. 常见优化模式
graph TD
A[用户请求] --> B{缓存检查}
B -->|命中| C[返回缓存资源]
B -->|未命中| D[请求服务器]
D --> E{资源类型}
E -->|静态资源| F[CDN 响应]
E -->|动态内容| G[服务器处理]
F --> H[存入浏览器缓存]
G --> I[生成响应]
H --> C
I --> C
9. 优化清单
| 优化项 | 目标 | 优先级 |
|---|---|---|
| LCP | < 2.5s | 高 |
| FID | < 100ms | 高 |
| CLS | < 0.1 | 高 |
| 图片压缩 | WebP + 响应式 | 高 |
| 代码拆分 | 按路由拆分 | 高 |
| 缓存策略 | 静态资源一年 | 中 |
| 懒加载 | 非首屏资源 | 中 |
| CDN | 全球节点 | 低 |
10. 总结
前端性能优化是一个系统工程,需要从多个维度入手:
- 指标驱动: 以 Web Vitals 为核心指标
- 用户体验: 关注实际加载和交互体验
- 渐进增强: 优先优化关键渲染路径
- 持续监控: 建立性能监控体系
注意: 优化要有的放矢,过度优化会增加维护成本。使用 Lighthouse 定期检测,关注对用户影响最大的问题。