name: 性能优化 description: 优化网络性能以实现更快加载和更好的用户体验。当被问及“加速我的网站”、“优化性能”、“减少加载时间”、“修复慢加载”、“提高页面速度”或“性能审计”时使用。 license: MIT metadata: author: web-quality-skills version: “1.0”
性能优化
基于Lighthouse性能审计的深度性能优化。专注于加载速度、运行时效率和资源优化。
工作原理
- 识别代码和资源中的性能瓶颈
- 根据对核心Web要素的影响进行优先级排序
- 提供具体的优化方案和代码示例
- 使用前后指标测量改进效果
性能预算
| 资源 | 预算 | 理由 |
|---|---|---|
| 总页面大小 | < 1.5 MB | 3G网络下约4秒加载 |
| JavaScript(压缩后) | < 300 KB | 解析和执行时间 |
| CSS(压缩后) | < 100 KB | 渲染阻塞 |
| 首屏图像 | < 500 KB | LCP影响 |
| 字体 | < 100 KB | 防止FOIT/FOUT |
| 第三方资源 | < 200 KB | 不可控延迟 |
关键渲染路径
服务器响应
- TTFB < 800ms。 首字节时间应快速。使用CDN、缓存和高效后端。
- 启用压缩。 对文本资源使用Gzip或Brotli。优先Brotli(小15-20%)。
- HTTP/2或HTTP/3。 多路复用减少连接开销。
- 边缘缓存。 尽可能在CDN边缘缓存HTML。
资源加载
预连接到所需源:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
预加载关键资源:
<!-- LCP图像 -->
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<!-- 关键字体 -->
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
推迟非关键CSS:
<!-- 关键CSS内联 -->
<style>/* 首屏样式 */</style>
<!-- 非关键CSS -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
JavaScript优化
推迟非必要脚本:
<!-- 解析阻塞(避免) -->
<script src="/critical.js"></script>
<!-- 推迟(首选) -->
<script defer src="/app.js"></script>
<!-- 异步(用于独立脚本) -->
<script async src="/analytics.js"></script>
<!-- 模块(默认推迟) -->
<script type="module" src="/app.mjs"></script>
代码分割模式:
// 基于路由的分割
const Dashboard = lazy(() => import('./Dashboard'));
// 基于组件的分割
const HeavyChart = lazy(() => import('./HeavyChart'));
// 基于功能的分割
if (user.isPremium) {
const PremiumFeatures = await import('./PremiumFeatures');
}
摇树优化最佳实践:
// ❌ 导入整个库
import _ from 'lodash';
_.debounce(fn, 300);
// ✅ 仅导入所需部分
import debounce from 'lodash/debounce';
debounce(fn, 300);
图像优化
格式选择
| 格式 | 使用场景 | 浏览器支持 |
|---|---|---|
| AVIF | 照片,最佳压缩 | 92%+ |
| WebP | 照片,良好备用 | 97%+ |
| PNG | 透明图形 | 通用 |
| SVG | 图标、Logo、插图 | 通用 |
响应式图像
<picture>
<!-- 现代浏览器使用AVIF -->
<source
type="image/avif"
srcset="hero-400.avif 400w,
hero-800.avif 800w,
hero-1200.avif 1200w"
sizes="(max-width: 600px) 100vw, 50vw">
<!-- WebP备用 -->
<source
type="image/webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, 50vw">
<!-- JPEG备用 -->
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
width="1200"
height="600"
alt="英雄图像"
loading="lazy"
decoding="async">
</picture>
LCP图像优先级
<!-- 首屏LCP图像:急切加载,高优先级 -->
<img
src="hero.webp"
fetchpriority="high"
loading="eager"
decoding="sync"
alt="英雄">
<!-- 非首屏图像:懒加载 -->
<img
src="product.webp"
loading="lazy"
decoding="async"
alt="产品">
字体优化
加载策略
/* 系统字体堆栈作为备用 */
body {
font-family: '自定义字体', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
/* 防止不可见文本 */
@font-face {
font-family: '自定义字体';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* 或对非关键使用optional */
font-weight: 400;
font-style: normal;
unicode-range: U+0000-00FF; /* 子集到拉丁文 */
}
预加载关键字体
<link rel="preload" href="/fonts/heading.woff2" as="font" type="font/woff2" crossorigin>
可变字体
/* 一个文件替代多个权重 */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}
缓存策略
Cache-Control头
# HTML(短或无缓存)
Cache-Control: no-cache, must-revalidate
# 带哈希的静态资源(不可变)
Cache-Control: public, max-age=31536000, immutable
# 无哈希的静态资源
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
# API响应
Cache-Control: private, max-age=0, must-revalidate
Service Worker缓存
// 静态资源的缓存优先策略
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image' ||
event.request.destination === 'style' ||
event.request.destination === 'script') {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('static-v1').then((cache) => cache.put(event.request, clone));
return response;
});
})
);
}
});
运行时性能
避免布局抖动
// ❌ 强制多次重排
elements.forEach(el => {
const height = el.offsetHeight; // 读取
el.style.height = height + 10 + 'px'; // 写入
});
// ✅ 批量读取,然后批量写入
const heights = elements.map(el => el.offsetHeight); // 所有读取
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'; // 所有写入
});
防抖昂贵操作
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
// 防抖滚动/调整大小处理程序
window.addEventListener('scroll', debounce(handleScroll, 100));
使用requestAnimationFrame
// ❌ 可能导致卡顿
setInterval(animate, 16);
// ✅ 与显示刷新同步
function animate() {
// 动画逻辑
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
虚拟化长列表
// 对于超过100项的列表,仅渲染可见项
// 使用库如react-window、vue-virtual-scroller或原生CSS:
.virtual-list {
content-visibility: auto;
contain-intrinsic-size: 0 50px; /* 估计项高度 */
}
第三方脚本
加载策略
// ❌ 阻塞主线程
<script src="https://analytics.example.com/script.js"></script>
// ✅ 异步加载
<script async src="https://analytics.example.com/script.js"></script>
// ✅ 延迟到交互时加载
<script>
document.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
const script = document.createElement('script');
script.src = 'https://widget.example.com/embed.js';
document.body.appendChild(script);
observer.disconnect();
}
});
observer.observe(document.querySelector('#widget-container'));
});
</script>
外观模式
<!-- 交互前显示静态占位符 -->
<div class="youtube-facade"
data-video-id="abc123"
onclick="loadYouTube(this)">
<img src="/thumbnails/abc123.jpg" alt="视频标题">
<button aria-label="播放视频">▶</button>
</div>
测量
关键指标
| 指标 | 目标 | 工具 |
|---|---|---|
| LCP | < 2.5s | Lighthouse, CrUX |
| FCP | < 1.8s | Lighthouse |
| Speed Index | < 3.4s | Lighthouse |
| TBT | < 200ms | Lighthouse |
| TTI | < 3.8s | Lighthouse |
测试命令
# Lighthouse CLI
npx lighthouse https://example.com --output html --output-path report.html
# Web Vitals库
import {onLCP, onINP, onCLS} from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
参考资料
有关核心Web要素的具体优化,请参见核心Web要素。