Appearance
资源优化
资源优化是提升 Web 应用性能的关键环节,涉及对静态资源(如 JavaScript、CSS、图片等)的优化处理。本指南将详细介绍各种资源优化技术和最佳实践。
JavaScript 优化
代码分割和懒加载
javascript
// Webpack 配置实现代码分割
// webpack.config.js
module.exports = {
mode: 'production',
entry: {
main: './src/main.js',
vendor: './src/vendor.js'
},
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'
}
}
};
// 动态导入实现懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'));
const Dashboard = React.lazy(() => import('./Dashboard'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
<Dashboard />
</Suspense>
</div>
);
}
// 路由级别的懒加载
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function AppRouter() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
JavaScript 代码优化
javascript
// 1. 避免全局变量污染
(function(global) {
'use strict';
// 私有变量和函数
const privateVar = 'private';
function privateFunction() {
// 私有逻辑
}
// 公共接口
global.MyApp = {
init: function() {
// 初始化逻辑
},
publicMethod: function() {
// 公共方法
}
};
}(window));
// 2. 使用现代 JavaScript 特性优化
class OptimizedComponent {
constructor(options = {}) {
// 使用解构和默认值
this.config = {
timeout: 5000,
retries: 3,
...options
};
// 绑定方法以避免在事件处理器中重复绑定
this.handleClick = this.handleClick.bind(this);
this.handleResize = this.debounce(this.handleResize.bind(this), 250);
}
handleClick(event) {
// 优化的事件处理
event.preventDefault();
// 避免多次点击
if (this.isProcessing) return;
this.isProcessing = true;
// 执行操作
this.performAction()
.finally(() => {
this.isProcessing = false;
});
}
// 防抖函数
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 虚拟滚动优化大量列表项
virtualScroll(items, containerHeight, itemHeight) {
const startIndex = Math.floor(this.scrollOffset / itemHeight);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const endIndex = Math.min(startIndex + visibleCount + 5, items.length); // 额外加载5个作为缓冲
return items.slice(startIndex, endIndex).map((item, index) => ({
...item,
style: { transform: `translateY(${(startIndex + index) * itemHeight}px)` }
}));
}
}
// 3. 内存优化
class MemoryOptimizedClass {
constructor() {
this.cache = new Map();
this.eventListeners = new Set();
}
// 及时清理资源
destroy() {
// 清理缓存
this.cache.clear();
// 移除事件监听器
this.eventListeners.forEach(listener => {
listener.target.removeEventListener(listener.type, listener.handler);
});
this.eventListeners.clear();
// 断开引用
this.cache = null;
this.eventListeners = null;
}
// 使用 WeakMap 避免内存泄漏
static elementData = new WeakMap();
storeElementData(element, data) {
MemoryOptimizedClass.elementData.set(element, data);
}
getElementData(element) {
return MemoryOptimizedClass.elementData.get(element);
}
}
CSS 优化
CSS 代码分割和优化
css
/* 1. 关键 CSS 内联优化 */
/* 关键路径 CSS - 内联到 HTML 中 */
.critical {
display: block;
margin: 0 auto;
font-family: Arial, sans-serif;
}
/* 2. 非关键 CSS 异步加载 */
/* 使用 JavaScript 异步加载非关键 CSS */
/* 在页面加载后再加载 */
javascript
// 异步加载 CSS
function loadCSS(href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.media = 'print'; // 初始设置为打印媒体类型,避免阻塞渲染
link.onload = () => link.media = 'all'; // 加载完成后切换到所有媒体类型
document.head.appendChild(link);
}
// 预加载关键 CSS
function preloadCriticalCSS(href) {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = href;
document.head.appendChild(link);
}
// 使用示例
preloadCriticalCSS('/css/critical.css');
loadCSS('/css/non-critical.css');
CSS 选择器优化
css
/* 好的选择器 - 避免过度嵌套 */
.header { }
.header-nav { }
.header-nav-item { }
/* 避免低效的选择器 */
/*
div > ul > li > a /* 过度限定 */
* { margin: 0; } /* 通配符选择器 */
ul li a { } /* 过多遍历 */
*/
/* 使用 CSS 自定义属性优化主题切换 */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--font-size-base: 16px;
--transition-fast: 0.15s ease;
--transition-normal: 0.3s ease;
}
.button {
background-color: var(--primary-color);
transition: all var(--transition-normal);
}
.button:hover {
background-color: color-mix(in srgb, var(--primary-color) 80%, black);
}
图片优化
响应式图片实现
html
<!-- 1. 使用 srcset 实现响应式图片 -->
<img
src="/images/default.jpg"
srcset="/images/small.jpg 480w,
/images/medium.jpg 800w,
/images/large.jpg 1200w"
sizes="(max-width: 480px) 100vw,
(max-width: 800px) 50vw,
25vw"
alt="响应式图片示例"
loading="lazy"
>
<!-- 2. 使用 picture 元素提供多种格式 -->
<picture>
<source srcset="/images/image.webp" type="image/webp">
<source srcset="/images/image.avif" type="image/avif">
<img src="/images/image.jpg" alt="多种格式图片">
</picture>
<!-- 3. 使用 CSS 实现背景图片优化 -->
<div class="hero-image" style="background-image: url('/images/hero.webp')"></div>
css
/* 响应式背景图片 */
.hero-image {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
aspect-ratio: 16/9; /* 现代浏览器支持 */
}
/* 使用 CSS 自定义属性实现图片懒加载占位符 */
.image-container {
background-color: #f0f0f0;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23e0e0e0"/><path d="M20,20 L80,80 M80,20 L20,80" stroke="%23cccccc" stroke-width="2"/></svg>');
background-size: 50px 50px;
transition: opacity 0.3s ease;
}
.image-container.loaded {
opacity: 1;
}
图片懒加载实现
javascript
// Intersection Observer 实现图片懒加载
class ImageLazyLoader {
constructor(options = {}) {
this.options = {
rootMargin: options.rootMargin || '50px 0px',
threshold: options.threshold || 0.01,
...options
};
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
this.options
);
this.loadImages();
}
loadImages() {
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => this.observer.observe(img));
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
this.observer.unobserve(img);
}
});
}
loadImage(img) {
return new Promise((resolve, reject) => {
const imageLoader = new Image();
imageLoader.onload = () => {
img.src = img.dataset.src;
img.classList.add('loaded');
resolve(img);
};
imageLoader.onerror = () => {
img.src = img.dataset.error || '/images/placeholder.png';
reject(new Error(`Failed to load image: ${img.dataset.src}`));
};
imageLoader.src = img.dataset.src;
// 清理 data-src 属性
img.removeAttribute('data-src');
});
}
destroy() {
this.observer.disconnect();
}
}
// 使用懒加载
document.addEventListener('DOMContentLoaded', () => {
new ImageLazyLoader({
rootMargin: '100px 0px',
threshold: 0.1
});
});
Webpack 资源优化
高级 Webpack 配置
javascript
// webpack.production.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
mode: 'production',
entry: {
app: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
clean: true // 清理输出目录
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除 console
drop_debugger: true, // 移除 debugger
pure_funcs: ['console.log'] // 移除特定函数调用
},
mangle: {
reserved: ['$', 'jQuery'] // 保留特定名称
}
}
}),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: -10,
chunks: 'all'
}
}
},
runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3,
targets: '> 0.25%, not dead'
}]
],
plugins: [
'@babel/plugin-transform-runtime' // 避免污染全局作用域
]
}
}
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB 以下转为 base64
}
},
generator: {
filename: 'images/[name].[hash:8][ext]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[name].[contenthash:8].chunk.css'
}),
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html'
})
]
};
资源预加载和预获取
资源提示优化
html
<!-- 1. 预加载关键资源 -->
<link rel="preload" href="/fonts/critical-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/js/main.js" as="script">
<!-- 2. 预获取非关键资源 -->
<link rel="prefetch" href="/js/next-page.js">
<link rel="prefetch" href="/images/next-page-image.jpg">
<!-- 3. DNS 预解析 -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//api.example.com">
<!-- 4. 预连接 -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
javascript
// 动态资源预加载
class ResourcePreloader {
static preloadResource(href, as) {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = as;
link.href = href;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
}
static async preloadCriticalResources() {
const resources = [
{ href: '/css/main.css', as: 'style' },
{ href: '/js/app.js', as: 'script' },
{ href: '/fonts/main.woff2', as: 'font', type: 'font/woff2', crossorigin: true }
];
await Promise.all(
resources.map(resource => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = resource.as;
link.href = resource.href;
if (resource.type) link.type = resource.type;
if (resource.crossorigin) link.crossOrigin = resource.crossorigin;
document.head.appendChild(link);
})
);
}
// 预获取可能需要的资源
static prefetchResource(href) {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = href;
document.head.appendChild(link);
}
}
// 页面加载完成后预获取后续页面资源
window.addEventListener('load', () => {
ResourcePreloader.preloadCriticalResources();
// 预获取用户可能访问的页面资源
setTimeout(() => {
ResourcePreloader.prefetchResource('/js/dashboard.js');
ResourcePreloader.prefetchResource('/js/profile.js');
}, 2000);
});
资源缓存策略
智能缓存实现
javascript
// 资源缓存管理器
class ResourceManager {
constructor() {
this.cache = new Map();
this.maxCacheSize = 100;
}
// 缓存静态资源
async loadResource(url, options = {}) {
// 检查缓存
if (this.cache.has(url)) {
const cached = this.cache.get(url);
if (Date.now() - cached.timestamp < (options.maxAge || 300000)) { // 5分钟
return cached.data;
}
}
// 加载资源
const response = await fetch(url);
const data = await response.text();
// 存入缓存
this.setCache(url, data, options.maxAge);
return data;
}
setCache(url, data, maxAge = 300000) {
if (this.cache.size >= this.maxCacheSize) {
// 简单的 FIFO 清理
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(url, {
data,
timestamp: Date.now(),
maxAge
});
}
// 预加载资源到缓存
async preloadResources(urls) {
const promises = urls.map(url =>
this.loadResource(url).catch(err => {
console.warn(`Failed to preload ${url}:`, err);
})
);
await Promise.all(promises);
}
}
// 使用资源管理器
const resourceManager = new ResourceManager();
// 预加载关键资源
resourceManager.preloadResources([
'/api/config',
'/css/theme.css',
'/js/utils.js'
]);
资源优化监控
性能监控
javascript
// 资源加载性能监控
class ResourcePerformanceMonitor {
constructor() {
this.metrics = {
resourceLoadTimes: [],
cacheHitRate: 0,
totalSize: 0,
compressedSize: 0
};
}
// 监控资源加载
monitorResourceLoad(resourceUrl) {
const startMark = `resource-start-${resourceUrl}`;
const endMark = `resource-end-${resourceUrl}`;
performance.mark(startMark);
return (loadTime, size) => {
performance.mark(endMark);
performance.measure(`resource-${resourceUrl}`, startMark, endMark);
this.metrics.resourceLoadTimes.push({
url: resourceUrl,
loadTime,
size,
timestamp: Date.now()
});
this.metrics.totalSize += size;
};
}
// 获取性能指标
getMetrics() {
const entries = performance.getEntriesByType('measure');
const loadTimes = entries
.filter(entry => entry.name.startsWith('resource-'))
.map(entry => entry.duration);
const avgLoadTime = loadTimes.reduce((a, b) => a + b, 0) / loadTimes.length;
return {
...this.metrics,
averageLoadTime: avgLoadTime,
resourceCount: entries.length
};
}
// 资源优化建议
getOptimizationSuggestions() {
const metrics = this.getMetrics();
const suggestions = [];
if (metrics.averageLoadTime > 1000) {
suggestions.push('资源加载时间过长,考虑压缩或 CDN');
}
if (metrics.totalSize > 1024 * 1024) { // 1MB
suggestions.push('资源总大小过大,考虑代码分割');
}
return suggestions;
}
}
const perfMonitor = new ResourcePerformanceMonitor();
// 监控脚本加载
function loadScriptWithMonitoring(src) {
const monitor = perfMonitor.monitorResourceLoad(src);
const startTime = Date.now();
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = () => {
const loadTime = Date.now() - startTime;
monitor(loadTime, 0); // 简化处理,实际应该获取文件大小
resolve(script);
};
script.onerror = reject;
document.head.appendChild(script);
});
}
资源优化检查清单
- [ ] 实现 JavaScript 代码分割
- [ ] 使用动态导入实现懒加载
- [ ] 优化 CSS 选择器性能
- [ ] 实现响应式图片
- [ ] 使用 WebP/AVIF 等现代图片格式
- [ ] 实现图片懒加载
- [ ] 配置资源预加载和预获取
- [ ] 使用 CDN 分发静态资源
- [ ] 实现适当的缓存策略
- [ ] 压缩和最小化资源文件
- [ ] 移除未使用的代码(Tree Shaking)
- [ ] 使用资源提示优化加载顺序
- [ ] 监控和分析资源加载性能
- [ ] 实施错误处理和降级策略
通过实施这些资源优化策略,您可以显著提升 Web 应用的加载速度和用户体验。