请求合并与缓存策略
减少HTTP请求次数和有效利用缓存是前端性能优化的关键手段。
请求合并策略
资源合并
JavaScript
// ❌ 多个小文件单独请求
<script src="a.js"></script>
<script src="b.js"></script>
<script src="c.js"></script>
// ✅ 合并为单个文件
<script src="bundle.js"></script>
// Webpack配置
module.exports = {
optimization: {
splitChunks: {
minSize: 20000, // 小于20KB不分割,保持合并
}
}
};
图片合并(Sprite)
JavaScript
// CSS Sprite
.icon-home {
background-image: url('sprite.png');
background-position: 0 0;
width: 20px;
height: 20px;
}
.icon-user {
background-image: url('sprite.png');
background-position: -20px 0;
width: 20px;
height: 20px;
}
// 工具生成
// webpack-spritesmith / gulp.spritesmith
API请求合并
JavaScript
// ❌ 多次单独请求
const user = await fetch('/api/user/1');
const profile = await fetch('/api/profile/1');
const orders = await fetch('/api/orders/1');
// ✅ 批量请求API
const data = await fetch('/api/batch', {
method: 'POST',
body: JSON.stringify({
requests: [
{ path: '/user/1' },
{ path: '/profile/1' },
{ path: '/orders/1' }
]
})
});
// GraphQL天然合并
const query = `
query {
user(id: 1) { name email }
profile(id: 1) { avatar bio }
orders(userId: 1) { id total }
}
`;
请求队列合并
JavaScript
class RequestQueue {
constructor(batchFn, delay = 50) {
this.queue = [];
this.batchFn = batchFn;
this.delay = delay;
this.timer = null;
}
add(request) {
return new Promise((resolve, reject) => {
this.queue.push({ request, resolve, reject });
if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.delay);
}
});
}
flush() {
this.timer = null;
if (this.queue.length === 0) return;
const batch = this.queue;
this.queue = [];
this.batchFn(batch.map(item => item.request))
.then(results => {
batch.forEach((item, i) => item.resolve(results[i]));
})
.catch(error => {
batch.forEach(item => item.reject(error));
});
}
}
// 使用:合并短时间内的多次请求
const userQueue = new RequestQueue(
async (ids) => {
const response = await fetch(`/api/users/batch`, {
body: JSON.stringify(ids)
});
return response.json();
},
50 // 50ms内请求合并
);
// 多次调用会合并
userQueue.add(1);
userQueue.add(2);
userQueue.add(3); // 合并为一次请求 [1,2,3]
缓存策略设计
HTTP缓存
| 缓存类型 | 特点 | 适用场景 |
|---|---|---|
| 强缓存 | 不发请求直接用缓存 | 静态资源 |
| 协商缓存 | 询问服务器是否可用 | 动态数据 |
JavaScript
// 强缓存配置(服务端)
// Cache-Control: max-age=31536000, immutable
// 长期缓存静态资源
// 协商缓存配置
// ETag: "abc123"
// Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
// Webpack文件名hash
output: {
filename: '[name].[contenthash:8].js' // 内容hash,更新则新文件
}
// HTML不缓存
Cache-Control: no-cache // 每次协商
本地存储缓存
JavaScript
// 内存缓存(最快)
const memoryCache = new Map();
function getWithMemory(key, fetcher) {
if (memoryCache.has(key)) {
return memoryCache.get(key);
}
const value = fetcher();
memoryCache.set(key, value);
return value;
}
// localStorage缓存
function getWithLocalStorage(key, fetcher, ttl = 3600000) {
const cached = localStorage.getItem(key);
if (cached) {
const { value, timestamp } = JSON.parse(cached);
if (Date.now() - timestamp < ttl) {
return value;
}
}
const value = await fetcher();
localStorage.setItem(key, JSON.stringify({
value,
timestamp: Date.now()
}));
return value;
}
// IndexedDB缓存大数据
async function cacheLargeData(key, data) {
const db = await openDB('cacheDB');
await db.put('cache', { key, data, timestamp: Date.now() });
}
Service Worker缓存
JavaScript
// service-worker.js
const CACHE_NAME = 'v1';
const STATIC_ASSETS = [
'/',
'/styles.css',
'/bundle.js'
];
// 安装时缓存静态资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_ASSETS))
);
});
// 请求拦截
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(cached => {
if (cached) return cached;
return fetch(event.request)
.then(response => {
// 缓存新响应
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, response.clone()));
return response;
});
})
);
});
// 缓存策略:Cache First
async function cacheFirst(request) {
const cached = await caches.match(request);
return cached || fetch(request);
}
// 缓存策略:Network First
async function networkFirst(request) {
try {
const response = await fetch(request);
caches.open(CACHE_NAME).then(cache => cache.put(request, response.clone()));
return response;
} catch {
return caches.match(request);
}
}
// 缓存策略:Stale While Revalidate
async function staleWhileRevalidate(request) {
const cached = await caches.match(request);
const fetchPromise = fetch(request)
.then(response => {
caches.open(CACHE_NAME).then(cache => cache.put(request, response.clone()));
return response;
});
return cached || fetchPromise;
}
多级缓存架构
缓存层级设计
JavaScript
class MultiLevelCache {
constructor() {
this.memoryCache = new Map(); // L1: 内存
this.localStorageCache = new LocalStorageCache(); // L2: 本地存储
this.serviceWorkerCache = new ServiceWorkerCache(); // L3: Service Worker
}
async get(key) {
// L1检查
if (this.memoryCache.has(key)) {
return this.memoryCache.get(key);
}
// L2检查
const localData = await this.localStorageCache.get(key);
if (localData) {
this.memoryCache.set(key, localData);
return localData;
}
// L3检查
const swData = await this.serviceWorkerCache.get(key);
if (swData) {
this.localStorageCache.set(key, swData);
this.memoryCache.set(key, swData);
return swData;
}
// 网络请求
const networkData = await this.fetchNetwork(key);
this.set(key, networkData);
return networkData;
}
set(key, value) {
this.memoryCache.set(key, value);
this.localStorageCache.set(key, value);
this.serviceWorkerCache.set(key, value);
}
invalidate(key) {
this.memoryCache.delete(key);
this.localStorageCache.delete(key);
this.serviceWorkerCache.delete(key);
}
}
数据缓存管理
JavaScript
// 带过期时间缓存
class TTLCache {
constructor(defaultTTL = 60000) {
this.cache = new Map();
this.defaultTTL = defaultTTL;
}
set(key, value, ttl = this.defaultTTL) {
this.cache.set(key, {
value,
expiresAt: Date.now() + ttl
});
// 清理过期
this.cleanup();
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiresAt) {
this.cache.delete(key);
return null;
}
return item.value;
}
cleanup() {
for (const [key, item] of this.cache) {
if (Date.now() > item.expiresAt) {
this.cache.delete(key);
}
}
}
}
API缓存拦截
JavaScript
// Axios请求缓存拦截
const cache = new Map();
axios.interceptors.request.use(config => {
if (config.method === 'get' && config.cache) {
const cacheKey = `${config.url}${JSON.stringify(config.params)}`;
if (cache.has(cacheKey)) {
return Promise.reject({ cached: true, data: cache.get(cacheKey) });
}
}
return config;
});
axios.interceptors.response.use(response => {
if (response.config.method === 'get' && response.config.cache) {
const cacheKey = `${response.config.url}${JSON.stringify(response.config.params)}`;
cache.set(cacheKey, response.data);
}
return response;
}, error => {
if (error.cached) {
return Promise.resolve({ data: error.data });
}
return Promise.reject(error);
});
// 使用
axios.get('/api/data', { cache: true, ttl: 60000 });
缓存失效策略
主动失效
JavaScript
// 数据更新时清除缓存
async function updateUser(user) {
const response = await fetch('/api/user', {
method: 'PUT',
body: JSON.stringify(user)
});
// 清除相关缓存
cache.invalidate(`user-${user.id}`);
cache.invalidate('user-list');
return response;
}
版本号缓存
JavaScript
// 带版本号缓存
const CACHE_VERSION = 'v2';
function getCacheKey(key) {
return `${CACHE_VERSION}:${key}`;
}
// 版本更新时自动失效旧缓存
localStorage.getItem('v1:user-1'); // 不会被新版本读取
localStorage.getItem('v2:user-1'); // 新版本数据
最大缓存限制
JavaScript
class SizeLimitedCache {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
}
set(key, value) {
if (this.cache.size >= this.maxSize) {
// 删除最老的条目
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
要点总结
- 请求合并:资源合并、批量API、请求队列去抖
- 强缓存:Cache-Control配置静态资源长期缓存
- 协商缓存:ETag/Last-Modified动态数据校验
- 多级缓存:内存→LocalStorage→Service Worker→网络
- 缓存失效:主动清除、版本号、大小限制
- Service Worker:离线缓存、拦截请求、多策略切换
存放路径:articles/JS/专家/高级性能分析/请求合并与缓存策略.md
📝 发现内容有误?点击此处直接编辑