Appearance
前端性能优化
前端性能优化是提升用户体验和网站加载速度的关键。本章将详细介绍各种前端性能优化技术和最佳实践。
资源加载优化
资源预加载策略
html
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//api.example.com">
<!-- 预连接 -->
<link rel="preconnect" href="https://fonts.gstatic.com">
<link rel="preconnect" href="https://api.example.com">
<!-- 资源预加载 -->
<link rel="preload" href="/critical-styles.css" as="style">
<link rel="preload" href="/hero-image.jpg" as="image">
<link rel="preload" href="/critical-font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 资源预获取 -->
<link rel="prefetch" href="/next-page.html">
<link rel="prefetch" href="/secondary-script.js">
JavaScript 加载优化
javascript
// 1. 使用 defer 和 async 属性
// defer: 脚本按顺序执行,在 DOM 解析完成后
// async: 脚本异步加载,加载完成后立即执行
const scriptLoader = {
// 动态加载脚本
loadScript: function(src, options = {}) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
if (options.async) script.async = true;
if (options.defer) script.defer = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
},
// 按需加载模块
loadModule: async function(modulePath) {
const module = await import(modulePath);
return module;
},
// 脚本加载队列
loadQueue: async function(scripts) {
for (const script of scripts) {
await this.loadScript(script);
}
}
};
// 2. 脚本分割和懒加载
class CodeSplitter {
constructor() {
this.loadedChunks = new Set();
}
// 动态导入实现懒加载
async loadFeature(featureName) {
if (this.loadedChunks.has(featureName)) {
return this.loadedChunks.get(featureName);
}
let module;
switch(featureName) {
case 'chart':
module = await import('./features/chart.js');
break;
case 'editor':
module = await import('./features/editor.js');
break;
case 'gallery':
module = await import('./features/gallery.js');
break;
default:
throw new Error(`Unknown feature: ${featureName}`);
}
this.loadedChunks.set(featureName, module);
return module;
}
// 预加载功能模块
preloadFeature(featureName) {
// 使用 Intersection Observer 预加载即将需要的功能
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadFeature(featureName);
observer.unobserve(entry.target);
}
});
});
// 观察需要该功能的元素
const featureTrigger = document.querySelector(`[data-feature="${featureName}"]`);
if (featureTrigger) {
observer.observe(featureTrigger);
}
} else {
// 降级处理:立即加载
this.loadFeature(featureName);
}
}
}
CSS 加载优化
css
/* 1. 关键 CSS 内联 */
<style>
/* 关键路径 CSS - 首屏渲染必需 */
.header {
height: 60px;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.hero {
height: 400px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
/* 2. 非关键 CSS 异步加载 */
<link rel="preload" href="/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/non-critical.css"></noscript>
/* 3. CSS 代码分割 */
/* 基础样式 */
@import url('./base.css');
/* 组件样式 - 按需加载 */
@import url('./components/button.css');
@import url('./components/form.css');
/* 页面特定样式 */
@import url('./pages/home.css');
图片优化
响应式图片
html
<!-- 1. 使用 srcset 和 sizes -->
<img
srcset="image-320w.jpg 320w,
image-640w.jpg 640w,
image-1280w.jpg 1280w"
sizes="(max-width: 320px) 280px,
(max-width: 640px) 600px,
1200px"
src="image-1280w.jpg"
alt="描述文字">
<!-- 2. 使用 picture 元素 -->
<picture>
<source media="(min-width: 1200px)" srcset="large-image.webp" type="image/webp">
<source media="(min-width: 1200px)" srcset="large-image.jpg">
<source media="(min-width: 640px)" srcset="medium-image.webp" type="image/webp">
<source media="(min-width: 640px)" srcset="medium-image.jpg">
<img src="small-image.jpg" alt="响应式图片">
</picture>
<!-- 3. 懒加载图片 -->
<img
data-src="image.jpg"
data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
class="lazy-image"
alt="懒加载图片">
图片懒加载实现
javascript
class ImageLazyLoader {
constructor() {
this.imageObserver = null;
this.init();
}
init() {
if ('IntersectionObserver' in window) {
this.imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '50px 0px', // 提前 50px 开始加载
threshold: 0.01
});
// 观察所有懒加载图片
document.querySelectorAll('img[data-src]').forEach(img => {
this.imageObserver.observe(img);
});
} else {
// 降级处理:立即加载所有图片
document.querySelectorAll('img[data-src]').forEach(img => {
this.loadImage(img);
});
}
}
loadImage(img) {
// 设置加载中的占位符
img.src = this.getPlaceholder(img);
// 创建新图片对象预加载
const imageLoader = new Image();
imageLoader.onload = () => {
// 替换为真实图片
img.src = img.dataset.src;
img.srcset = img.dataset.srcset || '';
// 触发加载完成事件
img.dispatchEvent(new CustomEvent('lazyloaded'));
};
imageLoader.onerror = () => {
// 加载失败,显示错误占位符
img.src = this.getErrorPlaceholder();
img.dispatchEvent(new CustomEvent('lazyerror'));
};
imageLoader.src = img.dataset.src;
}
getPlaceholder(img) {
// 根据图片尺寸生成小尺寸占位符或使用 SVG 占位符
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2VlZSIvPjwvc3ZnPg==';
}
getErrorPlaceholder() {
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2NjYyIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkltYWdlIEVycm9yPC90ZXh0Pjwvc3ZnPg==';
}
}
// 初始化图片懒加载
document.addEventListener('DOMContentLoaded', () => {
new ImageLazyLoader();
});
缓存策略
HTTP 缓存
javascript
// 1. Service Worker 缓存策略
const CACHE_NAME = 'app-v1.2.3';
const STATIC_CACHE = [
'/',
'/css/app.css',
'/js/app.js',
'/images/logo.png'
];
// 安装 Service Worker
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_CACHE))
.then(() => self.skipWaiting())
);
});
// 拦截请求并使用缓存
self.addEventListener('fetch', event => {
const { request } = event;
// 对于 HTML 页面,使用网络优先策略
if (request.destination === 'document') {
event.respondWith(networkFirst(request));
}
// 对于静态资源,使用缓存优先策略
else if (isStaticAsset(request)) {
event.respondWith(cacheFirst(request));
}
// 对于 API 请求,使用网络优先但降级到缓存
else {
event.respondWith(networkFirst(request));
}
});
// 缓存优先策略
async function cacheFirst(request) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
}
// 网络优先策略
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
throw error;
}
}
function isStaticAsset(request) {
return request.destination === 'script' ||
request.destination === 'style' ||
request.destination === 'image';
}
浏览器缓存头配置
javascript
// Express.js 中的缓存头配置
const express = require('express');
const app = express();
// 静态资源缓存
app.use('/static', express.static('public', {
maxAge: '1y', // 静态资源长期缓存
etag: true,
lastModified: true
}));
// 版本化静态资源
app.get('/assets/:version/*', (req, res, next) => {
// 设置长期缓存
res.set('Cache-Control', 'public, max-age=31536000'); // 1年
express.static('public')(req, res, next);
});
// API 响应缓存
app.get('/api/data', (req, res) => {
const data = getData();
// 设置 ETag
res.set('ETag', generateETag(data));
// 检查 If-None-Match
if (req.headers['if-none-match'] === generateETag(data)) {
return res.status(304).end();
}
res.json(data);
});
// 生成 ETag
function generateETag(data) {
const crypto = require('crypto');
return crypto.createHash('md5').update(JSON.stringify(data)).digest('hex');
}
// 获取数据函数
function getData() {
// 实际的数据获取逻辑
return { timestamp: Date.now(), data: 'example' };
}
渲染性能优化
虚拟滚动
javascript
class VirtualScroll {
constructor(container, itemHeight, totalItems, renderItem) {
this.container = container;
this.itemHeight = itemHeight;
this.totalItems = totalItems;
this.renderItem = renderItem;
this.visibleStart = 0;
this.visibleEnd = 0;
this.init();
}
init() {
this.container.style.height = `${this.totalItems * this.itemHeight}px`;
this.container.style.position = 'relative';
this.container.style.overflow = 'auto';
this.itemsContainer = document.createElement('div');
this.itemsContainer.style.position = 'absolute';
this.itemsContainer.style.top = '0';
this.itemsContainer.style.left = '0';
this.itemsContainer.style.width = '100%';
this.container.appendChild(this.itemsContainer);
this.container.addEventListener('scroll', this.onScroll.bind(this));
this.onScroll(); // 初始化
}
onScroll() {
const scrollTop = this.container.scrollTop;
const containerHeight = this.container.clientHeight;
this.visibleStart = Math.floor(scrollTop / this.itemHeight);
this.visibleEnd = Math.min(
this.totalItems - 1,
Math.ceil((scrollTop + containerHeight) / this.itemHeight)
);
// 计算需要渲染的项目数量
const itemsToRender = this.visibleEnd - this.visibleStart + 1;
// 清空现有项目
this.itemsContainer.innerHTML = '';
// 渲染可见项目
for (let i = this.visibleStart; i <= this.visibleEnd; i++) {
const item = this.renderItem(i);
item.style.position = 'absolute';
item.style.top = `${i * this.itemHeight}px`;
item.style.left = '0';
item.style.width = '100%';
item.style.height = `${this.itemHeight}px`;
this.itemsContainer.appendChild(item);
}
}
}
// 使用示例
const container = document.getElementById('virtual-scroll-container');
const virtualScroll = new VirtualScroll(
container,
50, // 每项高度
10000, // 总项目数
(index) => {
// 渲染单个项目
const div = document.createElement('div');
div.textContent = `Item ${index}`;
div.style.borderBottom = '1px solid #eee';
div.style.padding = '10px';
return div;
}
);
防抖和节流
javascript
// 防抖函数
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流函数
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 实际应用示例
class PerformanceOptimizedComponent {
constructor() {
// 防抖搜索
this.debouncedSearch = debounce(this.performSearch.bind(this), 300);
// 节流滚动事件
this.throttledScroll = throttle(this.handleScroll.bind(this), 100);
this.init();
}
init() {
// 搜索框事件
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (e) => {
this.debouncedSearch(e.target.value);
});
// 滚动事件
window.addEventListener('scroll', this.throttledScroll);
}
performSearch(query) {
if (query.length < 2) return;
// 执行搜索逻辑
console.log('Searching for:', query);
// 实际的搜索实现
}
handleScroll() {
// 处理滚动逻辑
const scrollPosition = window.scrollY;
console.log('Scroll position:', scrollPosition);
// 实际的滚动处理逻辑
}
}
Webpack 构建优化
代码分割配置
javascript
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
publicPath: '/'
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 分离第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10
},
// 分离公共代码
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 5,
reuseExistingChunk: true
},
// 分离大的第三方库
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
chunks: 'all',
priority: 20
}
}
},
// 运行时代码分离
runtimeChunk: {
name: 'runtime'
}
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
chunks: ['main', 'runtime', 'vendors']
})
]
};
动态导入优化
javascript
// 路由级别的代码分割
const routes = [
{
path: '/',
component: () => import('./pages/Home.js')
},
{
path: '/about',
component: () => import('./pages/About.js')
},
{
path: '/dashboard',
component: () => import('./pages/Dashboard.js')
}
];
// 组件级别的代码分割
const LazyComponent = React.lazy(() => import('./ExpensiveComponent'));
function App() {
return (
<div>
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
</div>
);
}
// 功能模块的懒加载
class FeatureLoader {
async loadChartModule() {
const { Chart } = await import('./modules/chart');
return Chart;
}
async loadEditorModule() {
const { Editor } = await import('./modules/editor');
return Editor;
}
// 预加载功能模块
preloadModules() {
// 在空闲时间预加载
if ('requestIdleCallback' in window) {
requestIdleCallback(async () => {
await Promise.allSettled([
import('./modules/chart'),
import('./modules/editor'),
import('./modules/gallery')
]);
});
}
}
}
性能监控
Web Vitals 监控
javascript
// Core Web Vitals 监控
class WebVitalsMonitor {
constructor() {
this.metrics = {
lcp: null,
fcp: null,
cls: null,
fid: null,
ttfb: null
};
this.init();
}
init() {
// Largest Contentful Paint (LCP)
import('web-vitals').then(({ getLCP }) => {
getLCP(metric => {
this.metrics.lcp = metric;
this.reportMetric('LCP', metric);
});
});
// First Contentful Paint (FCP)
import('web-vitals').then(({ getFCP }) => {
getFCP(metric => {
this.metrics.fcp = metric;
this.reportMetric('FCP', metric);
});
});
// Cumulative Layout Shift (CLS)
import('web-vitals').then(({ getCLS }) => {
getCLS(metric => {
this.metrics.cls = metric;
this.reportMetric('CLS', metric);
});
});
// First Input Delay (FID)
import('web-vitals').then(({ getFID }) => {
getFID(metric => {
this.metrics.fid = metric;
this.reportMetric('FID', metric);
});
});
// Time to First Byte (TTFB)
import('web-vitals').then(({ getTTFB }) => {
getTTFB(metric => {
this.metrics.ttfb = metric;
this.reportMetric('TTFB', metric);
});
});
}
reportMetric(name, metric) {
// 发送指标到分析服务
console.log(`${name}:`, metric.value);
// 可以发送到服务器或分析工具
this.sendToAnalytics({
metric: name,
value: metric.value,
id: metric.id,
navigationType: metric.navigationType
});
}
sendToAnalytics(data) {
// 使用 sendBeacon 确保数据发送
if (navigator.sendBeacon) {
navigator.sendBeacon('/analytics', JSON.stringify(data));
} else {
// 降级处理
fetch('/analytics', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
}).catch(err => console.error('Analytics error:', err));
}
}
getMetrics() {
return this.metrics;
}
}
// 初始化监控
new WebVitalsMonitor();
实际优化案例
电商网站优化案例
javascript
// 电商网站性能优化实现
class EcommercePerformanceOptimizer {
constructor() {
this.resourceLoader = new ResourceLoader();
this.imageOptimizer = new ImageLazyLoader();
this.cacheManager = new CacheManager();
this.init();
}
init() {
// 1. 首屏优化
this.optimizeCriticalRenderingPath();
// 2. 资源预加载
this.preloadCriticalResources();
// 3. 非关键功能延迟加载
this.delayNonCriticalFeatures();
}
// 优化关键渲染路径
optimizeCriticalRenderingPath() {
// 内联关键 CSS
const criticalCSS = `
.header { height: 60px; background: #fff; }
.hero { height: 400px; display: flex; }
.product-card { display: block; }
`;
const style = document.createElement('style');
style.textContent = criticalCSS;
document.head.appendChild(style);
}
// 预加载关键资源
preloadCriticalResources() {
// 预加载首屏图片
const heroImages = document.querySelectorAll('.hero-image');
heroImages.forEach(img => {
this.preloadImage(img.src);
});
// 预加载关键字体
this.preloadFont('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
}
// 延迟非关键功能
delayNonCriticalFeatures() {
// 使用 Intersection Observer 延迟加载功能
const featureSections = document.querySelectorAll('[data-lazy-feature]');
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const featureName = entry.target.dataset.lazyFeature;
this.loadFeature(featureName);
observer.unobserve(entry.target);
}
});
});
featureSections.forEach(section => observer.observe(section));
}
}
async loadFeature(featureName) {
switch(featureName) {
case 'reviews':
const { Reviews } = await import('./features/reviews.js');
new Reviews();
break;
case 'recommendations':
const { Recommendations } = await import('./features/recommendations.js');
new Recommendations();
break;
case 'chat':
const { ChatWidget } = await import('./features/chat.js');
new ChatWidget();
break;
}
}
preloadImage(src) {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'image';
link.href = src;
document.head.appendChild(link);
}
preloadFont(href) {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = href;
document.head.appendChild(link);
}
}
// 启动优化器
document.addEventListener('DOMContentLoaded', () => {
new EcommercePerformanceOptimizer();
});
性能优化检查清单
javascript
// 前端性能优化检查清单
const performanceChecklist = {
// 加载性能
loading: [
'使用关键 CSS 内联首屏样式',
'延迟非关键 CSS 加载',
'使用 defer/async 加载 JavaScript',
'实现资源预加载策略',
'压缩和合并资源文件',
'使用 CDN 加速静态资源',
'启用 Gzip/Brotli 压缩'
],
// 渲染性能
rendering: [
'避免强制同步布局',
'使用 transform 和 opacity 实现动画',
'实现虚拟滚动处理大量数据',
'使用防抖和节流处理频繁事件',
'优化 DOM 操作',
'使用 DocumentFragment 批量更新'
],
// 网络性能
network: [
'实现图片懒加载',
'使用响应式图片',
'压缩图片资源',
'使用 WebP 格式',
'实现 HTTP 缓存策略',
'使用 Service Worker 离线缓存'
],
// 构建优化
build: [
'实现代码分割',
'使用 Tree Shaking 移除未使用代码',
'压缩 JavaScript 和 CSS',
'使用长期缓存策略',
'实现资源版本控制',
'使用构建时优化'
],
// 监控
monitoring: [
'监控 Core Web Vitals',
'实现性能指标收集',
'设置性能预算',
'定期进行性能审计',
'监控第三方脚本影响',
'实现错误和性能监控'
]
};
// 性能评分计算器
class PerformanceScoreCalculator {
calculateScore(category, itemsCompleted) {
const totalItems = performanceChecklist[category].length;
return Math.round((itemsCompleted / totalItems) * 100);
}
generateReport() {
const categories = Object.keys(performanceChecklist);
const report = {};
categories.forEach(category => {
report[category] = this.calculateScore(category, 0); // 需要实际的完成数量
});
return report;
}
}
总结
前端性能优化是一个系统性工程,需要从多个维度考虑:
- 资源加载优化 - 合理安排资源加载顺序和策略
- 渲染性能优化 - 优化 DOM 操作和动画性能
- 缓存策略 - 实现多层缓存机制
- 构建优化 - 优化打包和部署流程
- 监控分析 - 持续监控和优化性能指标
通过系统性的性能优化,可以显著提升用户体验和网站性能。