Web性能优化实战
性能就是用户体验。在这个注意力稀缺的时代,用户对网页加载速度的耐心越来越低。研究表明,如果网页加载时间超过3秒,超过一半的用户会离开。更快的网站不仅能提供更好的用户体验,还能提高搜索引擎排名和转化率。在这篇文章中,我将分享20多个实用的性能优化技巧,涵盖从资源加载到渲染优化的各个方面。
理解性能指标
在优化之前,我们需要理解如何衡量性能。Google提出的Core Web Vitals是目前最重要的指标体系:
- LCP(Largest Contentful Paint):最大内容绘制时间,衡量页面主要内容何时可见。目标值<2.5秒。
- FID(First Input Delay):首次输入延迟,衡量交互响应速度。目标值<100毫秒。
- CLS(Cumulative Layout Shift):累积布局偏移,衡量视觉稳定性。目标值<0.1。
除了Core Web Vitals,还有其他重要指标:TTFB(首字节时间)、FCP(首次内容绘制)、TTI(可交互时间)等。使用Lighthouse和WebPageTest等工具可以全面测量这些指标。
资源加载优化
1. 图片优化
图片通常占据网页传输大小的50%以上,优化图片是最有效的手段之一。
选择正确的格式:照片使用WebP或AVIF,图标和简单图形使用SVG,透明图片使用PNG,避免使用GIF动画(改用视频)。
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="描述" loading="lazy">
</picture>
懒加载:使用loading="lazy"属性让图片延迟加载,显著提升首屏速度。
响应式图片:根据设备提供不同尺寸的图片,避免移动设备下载大图。
<img
srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
sizes="(max-width: 600px) 480px, 800px"
src="medium.jpg"
alt="响应式图片"
>
2. 代码压缩与合并
压缩JavaScript、CSS和HTML可以显著减少文件大小。现代构建工具如Webpack、Vite都内置了压缩功能。
合并文件可以减少HTTP请求数,但要适度。HTTP/2多路复用使得合并的重要性降低,过多合并反而影响缓存效果。
3. 使用CDN
内容分发网络(CDN)可以将资源缓存在全球各地的服务器上,用户从最近的服务器获取资源,大大减少延迟。对于静态资源,使用CDN是性价比最高的优化手段。
关键渲染路径优化
4. 消除渲染阻塞资源
CSS默认阻塞渲染,JavaScript默认阻塞解析。优化策略包括:
<!-- 异步加载JavaScript -->
<script src="analytics.js" async></script>
<!-- 延迟加载JavaScript -->
<script src="app.js" defer></script>
<!-- 内联关键CSS -->
<style>
/* 首屏关键样式 */
</style>
<!-- 异步加载剩余CSS -->
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
5. 减少重排重绘
重排(Reflow)和重绘(Repaint)是浏览器渲染过程中最昂贵的操作。批量修改DOM、使用transform代替位置属性、避免频繁读取布局属性都可以减少重排。
// 不好:触发多次重排
elements.forEach(el => {
el.style.width = el.offsetWidth + 10 + 'px';
});
// 好:先读取,后写入
const widths = elements.map(el => el.offsetWidth);
elements.forEach((el, i) => {
el.style.width = widths[i] + 10 + 'px';
});
缓存策略
6. 浏览器缓存
合理设置HTTP缓存头,让浏览器缓存静态资源:
# Apache .htaccess
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
</IfModule>
使用内容哈希作为文件名的一部分,实现缓存失效:app.abc123.js。这样内容变化时文件名变化,浏览器会请求新文件。
7. Service Worker缓存
Service Worker可以实现更精细的缓存控制,甚至离线访问:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(fetchResponse => {
return caches.open('v1').then(cache => {
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
});
});
})
);
});
JavaScript优化
8. 代码分割
将JavaScript拆分成多个块,按需加载:
// 动态导入
const module = await import('./heavy-module.js');
// React懒加载
const LazyComponent = React.lazy(() => import('./Component'));
// Vue异步组件
const AsyncComponent = defineAsyncComponent(() => import('./Component'));
9. Tree Shaking
使用ES模块语法,让打包工具消除未使用的代码:
// 不好:导入整个库
import _ from 'lodash';
// 好:按需导入
import { debounce, throttle } from 'lodash-es';
10. 避免长任务
长任务会阻塞主线程,导致交互无响应。使用Web Worker处理耗时计算,或使用时间切片:
async function processLargeArray(array) {
const CHUNK_SIZE = 1000;
for (let i = 0; i < array.length; i += CHUNK_SIZE) {
const chunk = array.slice(i, i + CHUNK_SIZE);
processChunk(chunk);
// 让出主线程
await new Promise(resolve => setTimeout(resolve, 0));
}
}
网络优化
11. HTTP/2和HTTP/3
HTTP/2的多路复用消除了并行请求的限制,头部压缩减少了开销。HTTP/3基于QUIC协议,进一步降低延迟。确保服务器支持并启用这些协议。
12. 预加载和预连接
<!-- 预连接到关键域名 -->
<link rel="preconnect" href="https://cdn.example.com">
<!-- 预加载关键资源 -->
<link rel="preload" href="critical-font.woff2" as="font" crossorigin>
<!-- 预取下一页资源 -->
<link rel="prefetch" href="next-page.html">
CSS优化
13. 关键CSS内联
将首屏渲染所需的CSS内联到HTML中,其余CSS异步加载。这可以显著改善FCP和LCP。
14. 避免昂贵的CSS选择器
/* 避免:从右向左匹配,效率低 */
.container .item > div p span { }
/* 使用:简单直接 */
.item-span { }
15. will-change优化动画
.animated-element {
will-change: transform, opacity;
}
will-change提示浏览器提前优化,但不要滥用,只在真正需要的元素上使用。
字体优化
16. 字体显示策略
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* 立即显示后备字体,字体加载完成后替换 */
}
17. 字体子集化
只包含网站实际使用的字符,可以大幅减少字体文件大小:
/* 使用unicode-range指定字符范围 */
@font-face {
font-family: 'SubsetFont';
src: url('font-latin.woff2') format('woff2');
unicode-range: U+0000-00FF;
}
性能监控
18. Real User Monitoring (RUM)
// 使用Performance API收集真实用户数据
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'largest-contentful-paint') {
sendToAnalytics({
lcp: entry.startTime,
url: location.href,
connection: navigator.connection.effectiveType
});
}
}
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
19. 性能预算
设定性能预算并在CI中检查:
// performance-budget.json
{
"budgets": [
{
"resourceType": "script",
"budget": 300
},
{
"resourceType": "total",
"budget": 1500
},
{
"metric": "largest-contentful-paint",
"budget": 2500
}
]
}
高级优化技巧
20. 服务端渲染(SSR)
对于内容密集型网站,SSR可以显著改善首次加载体验。用户更快看到内容,搜索引擎也能更好地索引。
21. 静态生成
对于内容不频繁变化的页面,预渲染为静态HTML是最佳选择。结合增量静态再生成(ISR),可以实现快速更新。
22. 流式传输
使用流式HTML传输,浏览器可以在接收HTML的同时开始渲染,不必等待完整响应。
总结
性能优化是一个持续的过程,不是一次性的工作。从最重要的优化开始:图片优化、缓存策略、代码分割。然后逐步深入:关键渲染路径、高级缓存、监控告警。
记住,优化的目的是改善用户体验,而不是追求数字。始终以真实用户数据为导向,在实验室数据的基础上,关注实际场景中的性能表现。
性能优化没有终点,但每一点改进都是对用户体验的投资。开始行动吧!