前端性能优化实战:那些年我踩过的坑
上周,我的老板拿着手机皱着眉头问我:“为什么我们的网站加载这么慢?“那一刻,我意识到是时候认真对待性能优化了。经过一个月的折腾,我把网站加载时间从8秒降到了2秒。今天想和大家分享一些实战经验,特别是那些我曾经踩过的坑。
先别急着优化,先搞清楚问题在哪
用数据说话,别凭感觉
刚开始我凭感觉优化了一堆东西,结果发现效果微乎其微。后来才知道,要先搞清楚瓶颈在哪里。
Google 的 Core Web Vitals 是个好东西,但别被这些指标吓到:
- LCP (最大内容绘制):简单说就是用户看到主要内容的时间,目标是2.5秒内
- FID (首次输入延迟):用户第一次点击页面到页面响应的时间,100毫秒内比较理想
- CLS (累积布局偏移):页面元素会不会乱跳动,数值越小越好
我用了 Lighthouse 和 Chrome DevTools 的 Performance 面板,发现主要问题是图片太大和JavaScript太多。
资源优化:图片是头号杀手
我的图片优化血泪史
我之前直接上传了4MB的原图,结果加载慢得要死。后来学乖了:
// 我现在用的懒加载组件
const LazyImage = ({ src, alt }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef} className="lazy-image-container">
{isInView && (
<img
src={src}
alt={alt}
onLoad={() => setIsLoaded(true)}
style={{ opacity: isLoaded ? 1 : 0 }}
/>
)}
</div>
);
};
这个组件帮我减少了60%的初始加载时间。还有,我现在都用 WebP 格式,体积比 JPEG 小30%左右。
代码分割:别让用户下载用不到的代码
我犯过的错误
之前我把整个管理后台的代码都打包进了主文件,结果普通用户也要下载几MB的管理代码。后来我学会了这样拆分:
// 路由级别的代码分割
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));
// 组件级别的动态导入
const HeavyComponent = lazy(() =>
import('./components/HeavyComponent').then(module => ({
default: module.HeavyComponent
}))
);
// 条件加载管理面板
const AdminPanel = lazy(() =>
import('./components/AdminPanel').then(module => ({
default: module.AdminPanel
}))
);
function App() {
const [isAdmin, setIsAdmin] = useState(false);
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<HomePage />
{isAdmin && (
<Suspense fallback={<div>Loading admin panel...</div>}>
<AdminPanel />
</Suspense>
)}
</Suspense>
</div>
);
}
这样拆分后,普通用户访问时只需要下载必要的代码,管理后台的代码只有在管理员登录时才会加载。
缓存策略:别让用户重复下载
Service Worker 救了我
我之前有个问题:用户每次访问都要重新下载所有资源。后来我加上了 Service Worker:
// 我的缓存策略
const CACHE_NAME = 'my-app-cache-v1';
const urlsToCache = [
'/',
'/static/js/main.js',
'/static/css/main.css',
'/static/images/logo.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中,直接返回
if (response) {
return response;
}
// 没命中,去网络请求
return fetch(event.request).then(response => {
// 检查响应是否有效
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应,因为响应流只能用一次
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
这个简单的缓存策略让我的重复访问速度提升了80%。
React 优化:别让组件瞎渲染
我踩过的 React 坑
我曾经有个列表页面,每次点击按钮整个列表都会重新渲染,性能差得要死。后来学会了这些技巧:
// 使用 React.memo 避免不必要的重渲染
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
// 组件逻辑
}, (prevProps, nextProps) => {
// 自定义比较函数,只有ID变化时才重新渲染
return prevProps.data.id === nextProps.data.id;
});
// 使用 useMemo 缓存计算结果
const ExpensiveCalculation = ({ items }) => {
const sortedItems = useMemo(() => {
console.log('Sorting items...');
return items.sort((a, b) => a.value - b.value);
}, [items]); // 只有items变化时才重新排序
return <div>{sortedItems.map(item => <div key={item.id}>{item.value}</div>)}</div>;
};
// 使用 useCallback 缓存函数
const ParentComponent = ({ items }) => {
const [count, setCount] = useState(0);
const handleItemClick = useCallback((item) => {
console.log('Item clicked:', item);
}, []); // 空依赖数组表示函数永远不会改变
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ItemList items={items} onItemClick={handleItemClick} />
</div>
);
};
这些优化让我的列表页面响应速度提升了3倍。
网络优化:让资源传输更快
HTTP/2 真的很有用
我之前还在用 HTTP/1.1,后来升级到 HTTP/2,发现多路复用真的很有用,可以同时加载多个资源。
还有资源预加载,我现在都会预加载关键资源:
<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/critical.css" as="style">
<!-- 预取可能需要的资源 -->
<link rel="prefetch" href="/next-page.js" as="script">
<link rel="prefetch" href="/next-page.css" as="style">
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//api.example.com">
<link rel="preconnect" href="//api.example.com" crossorigin>
这些简单的标签让我的页面加载速度又提升了20%。
图片优化:响应式图片是王道
我的图片优化经验
我现在都用响应式图片,根据设备尺寸加载不同大小的图片:
// 响应式图片组件
const ResponsiveImage = ({ src, alt, sizes }) => {
return (
<picture>
<source
media="(min-width: 1024px)"
srcSet={`${src}?w=1200&f=webp 1x, ${src}?w=2400&f=webp 2x`}
type="image/webp"
/>
<source
media="(min-width: 768px)"
srcSet={`${src}?w=800&f=webp 1x, ${src}?w=1600&f=webp 2x`}
type="image/webp"
/>
<img
src={`${src}?w=400&f=auto`}
alt={alt}
sizes={sizes}
loading="lazy"
decoding="async"
/>
</picture>
);
};
这样移动设备不会下载桌面端的大图,节省了大量带宽。
性能监控:别盲目优化
我的监控工具箱
我现在用这些工具监控性能:
- Lighthouse: 定期做性能审计
- WebPageTest: 分析不同地区的加载情况
- Chrome DevTools: 本地调试性能问题
- Real User Monitoring: 收集真实用户数据
我还设置了性能预算,防止代码膨胀:
{
"performanceBudget": {
"totalSize": "250KB",
"javascriptSize": "100KB",
"cssSize": "50KB",
"imageSize": "100KB",
"fontSize": "50KB",
"maxRequestCount": 20
}
}
总结:性能优化的心得
经过这一个月的折腾,我总结了几点心得:
- 先测量,再优化:别凭感觉,用数据说话
- 图片是头号杀手:优化图片往往效果最明显
- 懒加载是神器:别让用户下载用不到的东西
- 缓存很重要:合理的缓存策略能大幅提升体验
- 持续监控:性能优化是个持续的过程
最重要的是,性能优化不是一次性的工作,而是需要持续关注的过程。现在我每周都会检查性能数据,确保网站保持良好的状态。
希望我的经验能帮到大家,别再像我一样踩同样的坑了!