浏览器缓存(强缓存与协商缓存)
浏览器缓存是前端性能优化的重要手段,主要分为强缓存和协商缓存。
1. 缓存整体流程
graph TD
A[浏览器请求资源] --> B{检查缓存}
B -->|有缓存| C[强缓存验证]
B -->|无缓存| D[向服务器请求]
C --> E{Cache-Control/Expires}
E -->|未过期| F[直接使用缓存]
E -->|已过期| G[协商缓存验证]
G --> H{ETag/Last-Modified}
H -->|资源未修改| I[304 Not Modified]
H -->|资源已修改| J[200 OK 返回新资源]
F --> K[使用缓存]
I --> K
J --> L[更新缓存]
D --> L
K --> M[渲染页面]
L --> M
2. 强缓存
强缓存不会向服务器发送请求,直接从缓存中读取资源。
2.1 相关 HTTP 头部
Cache-Control (HTTP/1.1)
Cache-Control: max-age=3600
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: public
Cache-Control: private
常用指令:
max-age=3600:资源有效期为3600秒no-cache:禁用强缓存,使用协商缓存no-store:完全不缓存public:允许所有用户缓存private:仅允许单个用户缓存
Expires (HTTP/1.0)
Expires: Wed, 21 Oct 2023 07:28:00 GMT
2.2 强缓存示例
// 服务器设置强缓存
app.get('/static/js/app.js', (req, res) => {
res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年
res.sendFile('/path/to/app.js');
});
// 或者使用 Express static 中间件
app.use('/static', express.static('public', {
maxAge: '1y',
etag: false // 禁用 ETag,只使用强缓存
}));
2.3 强缓存验证流程
// 浏览器检查强缓存的伪代码
function checkStrongCache(request) {
const cache = getCache(request.url);
if (!cache) return null;
const cacheControl = cache.headers['cache-control'];
const expires = cache.headers['expires'];
// 优先检查 Cache-Control
if (cacheControl) {
if (cacheControl.includes('no-store')) return null;
if (cacheControl.includes('no-cache')) return null;
const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
if (maxAgeMatch) {
const maxAge = parseInt(maxAgeMatch[1]);
const age = Date.now() - cache.timestamp;
if (age < maxAge * 1000) {
return cache; // 命中强缓存
}
}
}
// 检查 Expires
if (expires) {
const expiresTime = new Date(expires).getTime();
if (Date.now() < expiresTime) {
return cache; // 命中强缓存
}
}
return null; // 强缓存失效
}
3. 协商缓存
当强缓存失效时,浏览器会向服务器发起验证请求,使用协商缓存。
3.1 相关 HTTP 头部
Last-Modified / If-Modified-Since
# 响应头
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
# 请求头
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT
ETag / If-None-Match
# 响应头
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# 请求头
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
3.2 协商缓存示例
// 服务器实现协商缓存
app.get('/api/data', (req, res) => {
const data = getDataFromDB();
const etag = generateETag(JSON.stringify(data));
// 检查 If-None-Match
const clientETag = req.headers['if-none-match'];
if (clientETag === etag) {
return res.status(304).end(); // 资源未修改
}
res.setHeader('ETag', etag);
res.setHeader('Last-Modified', new Date().toUTCString());
res.json(data);
});
// 生成 ETag 的函数
function generateETag(content) {
const crypto = require('crypto');
return crypto.createHash('md5').update(content).digest('hex');
}
3.3 协商缓存验证流程
// 协商缓存验证的伪代码
async function validateCache(request, cachedResponse) {
const headers = {};
// 添加 If-None-Match
if (cachedResponse.etag) {
headers['If-None-Match'] = cachedResponse.etag;
}
// 添加 If-Modified-Since
if (cachedResponse.lastModified) {
headers['If-Modified-Since'] = cachedResponse.lastModified;
}
try {
const response = await fetch(request.url, {
headers: headers,
method: 'HEAD' // 或者 GET
});
if (response.status === 304) {
// 资源未修改,使用缓存
return {
status: 'hit',
data: cachedResponse.data,
headers: response.headers
};
} else if (response.status === 200) {
// 资源已修改,返回新数据
const newData = await response.json();
return {
status: 'miss',
data: newData,
headers: response.headers
};
}
} catch (error) {
// 网络错误,降级使用缓存
return {
status: 'fallback',
data: cachedResponse.data
};
}
}
4. 实际配置示例
4.1 Nginx 配置
server {
listen 80;
server_name example.com;
# 静态资源 - 强缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Pragma "public";
add_header Vary "Accept-Encoding";
# 同时启用协商缓存
etag on;
}
# HTML 文件 - 协商缓存
location ~* \.(html|htm)$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
etag on;
}
# API 接口 - 不缓存或短时间缓存
location /api/ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
etag on;
}
}
4.2 Express.js 配置
const express = require('express');
const app = express();
// 静态资源 - 强缓存
app.use('/static', express.static('public', {
maxAge: '365d',
etag: true,
lastModified: true,
setHeaders: (res, path) => {
if (path.endsWith('.html')) {
// HTML 文件使用协商缓存
res.setHeader('Cache-Control', 'no-cache');
} else {
// 其他静态资源使用强缓存
res.setHeader('Cache-Control', 'public, max-age=31536000');
}
}
}));
// API 路由 - 协商缓存
app.get('/api/users', (req, res) => {
const users = getUsersFromDB();
const etag = generateETag(JSON.stringify(users));
// 检查客户端 ETag
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('ETag', etag);
res.json(users);
});
// 动态内容 - 不缓存
app.get('/dashboard', (req, res) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
res.render('dashboard');
});
4.3 Webpack 构建优化
// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[name].[contenthash].chunk.css',
}),
],
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
5. 缓存策略最佳实践
5.1 不同资源的缓存策略
| 资源类型 | 缓存策略 | 示例配置 |
|---|---|---|
| HTML 文件 | 协商缓存 | Cache-Control: no-cache |
| CSS/JS 文件 | 强缓存+版本号 | Cache-Control: max-age=31536000, immutable |
| 图片资源 | 强缓存 | Cache-Control: max-age=2592000 |
| API 响应 | 协商缓存/不缓存 | Cache-Control: no-cache |
| 用户数据 | 不缓存 | Cache-Control: no-store |
5.2 实际项目中的缓存配置
// 完整的缓存策略实现
class CacheStrategy {
static forResourceType(filename) {
const extension = filename.split('.').pop().toLowerCase();
switch (extension) {
case 'html':
return {
cacheControl: 'no-cache',
maxAge: 0,
etag: true
};
case 'css':
case 'js':
return {
cacheControl: 'public, max-age=31536000, immutable',
maxAge: 31536000,
etag: false
};
case 'png':
case 'jpg':
case 'jpeg':
case 'gif':
case 'svg':
case 'ico':
return {
cacheControl: 'public, max-age=2592000',
maxAge: 2592000,
etag: true
};
case 'woff':
case 'woff2':
case 'ttf':
case 'eot':
return {
cacheControl: 'public, max-age=31536000',
maxAge: 31536000,
etag: false
};
default:
return {
cacheControl: 'no-cache',
maxAge: 0,
etag: true
};
}
}
}
// 使用示例
app.use('*', (req, res, next) => {
const strategy = CacheStrategy.forResourceType(req.path);
res.setHeader('Cache-Control', strategy.cacheControl);
if (strategy.etag) {
res.setHeader('ETag', generateETag(req.path));
}
next();
});
6. 调试和监控
6.1 Chrome DevTools 查看缓存
// 在控制台检查资源缓存状态
function checkResourceCacheStatus(url) {
performance.getEntriesByName(url).forEach(entry => {
console.log('Resource:', entry.name);
console.log('Transfer Size:', entry.transferSize);
console.log('Encoded Body Size:', entry.encodedBodySize);
console.log('Decoded Body Size:', entry.decodedBodySize);
// 判断是否命中缓存
if (entry.transferSize === 0 && entry.encodedBodySize > 0) {
console.log('Status: ✅ 强缓存命中');
} else if (entry.transferSize > 0 && entry.transferSize < entry.encodedBodySize) {
console.log('Status: 🔄 协商缓存命中');
} else {
console.log('Status: ⬇️ 全新下载');
}
});
}
6.2 缓存命中率监控
// 缓存命中率统计
class CacheMonitor {
constructor() {
this.stats = {
strongCacheHits: 0,
negotiationCacheHits: 0,
misses: 0,
total: 0
};
}
recordRequest(transferSize, encodedSize) {
this.stats.total++;
if (transferSize === 0 && encodedSize > 0) {
this.stats.strongCacheHits++;
} else if (transferSize > 0 && transferSize < encodedSize) {
this.stats.negotiationCacheHits++;
} else {
this.stats.misses++;
}
}
getHitRate() {
const hits = this.stats.strongCacheHits + this.stats.negotiationCacheHits;
return (hits / this.stats.total * 100).toFixed(2);
}
getReport() {
return {
strongCacheHitRate: (this.stats.strongCacheHits / this.stats.total * 100).toFixed(2),
negotiationCacheHitRate: (this.stats.negotiationCacheHits / this.stats.total * 100).toFixed(2),
totalHitRate: this.getHitRate(),
totalRequests: this.stats.total
};
}
}
通过合理配置强缓存和协商缓存,可以显著提升网站性能,减少服务器负载,改善用户体验。
挣钱养家

浙公网安备 33010602011771号