缓存策略
缓存简介
缓存是提高Web性能的重要手段,通过存储资源副本减少网络请求,加快页面加载速度。
缓存类型
1. 强缓存
浏览器直接从本地缓存读取资源,不向服务器发送请求。
Cache-Control
http
Cache-Control: max-age=3600
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: public
Cache-Control: private
Cache-Control: must-revalidate
Cache-Control: immutableExpires
http
Expires: Wed, 21 Oct 2024 07:28:00 GMT示例
javascript
// 设置强缓存
fetch('https://example.com/style.css', {
headers: {
'Cache-Control': 'max-age=3600'
}
});2. 协商缓存
浏览器向服务器发送请求,服务器判断资源是否修改,返回304状态码或新资源。
ETag
http
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"Last-Modified
http
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT示例
javascript
// 协商缓存
fetch('https://example.com/data.json', {
headers: {
'If-None-Match': '"33a64df551425fcc55e4d42a148795d9f25f89d4"'
}
})
.then(response => {
if (response.status === 304) {
console.log('使用缓存');
} else {
return response.json();
}
});缓存策略
1. 不缓存
http
Cache-Control: no-store, no-cache, must-revalidate适用于:
- 敏感数据
- 实时数据
- 频繁变化的内容
2. 短期缓存
http
Cache-Control: max-age=60适用于:
- 新闻
- 社交媒体
- 实时更新
3. 长期缓存
http
Cache-Control: max-age=31536000, immutable适用于:
- 静态资源(CSS、JS、图片)
- 版本化资源
4. 协商缓存
http
Cache-Control: no-cache
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT适用于:
- HTML文件
- API响应
- 动态内容
Service Worker缓存
基本用法
javascript
// 注册Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker注册成功');
})
.catch(error => {
console.error('Service Worker注册失败:', error);
});
}缓存策略
javascript
// sw.js
const CACHE_NAME = 'my-cache-v1';
const urlsToCache = [
'/',
'/style.css',
'/script.js',
'/image.jpg'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
}
return fetch(event.request);
})
);
});缓存优先
javascript
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
);
});网络优先
javascript
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
const responseClone = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
return caches.match(event.request);
})
);
});缓存回退
javascript
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request)
.then(response => {
const responseClone = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
return caches.match('/offline.html');
});
})
);
});浏览器缓存
LocalStorage缓存
javascript
class Cache {
constructor(prefix = 'cache_') {
this.prefix = prefix;
}
set(key, value, ttl = 3600) {
const data = {
value: value,
expires: Date.now() + ttl * 1000
};
localStorage.setItem(this.prefix + key, JSON.stringify(data));
}
get(key) {
const data = localStorage.getItem(this.prefix + key);
if (!data) {
return null;
}
const parsed = JSON.parse(data);
if (Date.now() > parsed.expires) {
this.remove(key);
return null;
}
return parsed.value;
}
remove(key) {
localStorage.removeItem(this.prefix + key);
}
clear() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
localStorage.removeItem(key);
}
});
}
}
const cache = new Cache();
cache.set('user', { name: 'John', age: 30 }, 3600);
const user = cache.get('user');
console.log(user);IndexedDB缓存
javascript
class IndexedDBCache {
constructor(dbName, storeName) {
this.dbName = dbName;
this.storeName = storeName;
this.db = null;
}
async open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onerror = () => {
reject(request.error);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
const objectStore = db.createObjectStore(this.storeName, { keyPath: 'key' });
objectStore.createIndex('expires', 'expires', { unique: false });
}
};
});
}
async set(key, value, ttl = 3600) {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
const data = {
key: key,
value: value,
expires: Date.now() + ttl * 1000
};
return new Promise((resolve, reject) => {
const request = objectStore.put(data);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async get(key) {
const transaction = this.db.transaction([this.storeName], 'readonly');
const objectStore = transaction.objectStore(this.storeName);
return new Promise((resolve, reject) => {
const request = objectStore.get(key);
request.onsuccess = () => {
const data = request.result;
if (!data) {
resolve(null);
return;
}
if (Date.now() > data.expires) {
this.remove(key);
resolve(null);
return;
}
resolve(data.value);
};
request.onerror = () => reject(request.error);
});
}
async remove(key) {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const objectStore = transaction.objectStore(this.storeName);
return new Promise((resolve, reject) => {
const request = objectStore.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
const cache = new IndexedDBCache('CacheDB', 'cache');
cache.open().then(() => {
return cache.set('user', { name: 'John', age: 30 }, 3600);
}).then(() => {
return cache.get('user');
}).then(user => {
console.log(user);
});缓存优化
1. 版本化资源
html
<!-- 不好的做法 -->
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
<!-- 好的做法 -->
<link rel="stylesheet" href="style.css?v=1.0.0">
<script src="script.js?v=1.0.0"></script>2. 内容哈希
html
<!-- 使用内容哈希 -->
<link rel="stylesheet" href="style.abc123.css">
<script src="script.def456.js"></script>3. CDN缓存
html
<!-- 使用CDN -->
<script src="https://cdn.example.com/jquery.min.js"></script>4. 预加载
html
<!-- 预加载资源 -->
<link rel="preload" href="style.css" as="style">
<link rel="preload" href="script.js" as="script">面试常见问题
1. 强缓存和协商缓存的区别?
- 强缓存: 浏览器直接从本地缓存读取资源
- 协商缓存: 浏览器向服务器发送请求,服务器判断资源是否修改
2. Cache-Control的常用值?
http
Cache-Control: max-age=3600
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: public
Cache-Control: private3. ETag和Last-Modified的区别?
- ETag: 资源的唯一标识,更精确
- Last-Modified: 资源的最后修改时间,精度较低
4. Service Worker的缓存策略?
- 缓存优先
- 网络优先
- 缓存回退
5. 如何优化缓存?
- 版本化资源
- 使用内容哈希
- 使用CDN
- 预加载资源
通过理解缓存策略的各种技术和最佳实践,可以更好地优化Web性能。