静态资源本地缓存方式
文章类型:Vue
发布者:hp
发布时间:2026-07-09
在Web开发中,合理利用本地缓存可以显著提升页面加载速度、减少服务器压力、节省带宽成本。本文将全面介绍静态资源本地缓存的各种方式和最佳实践。
| 缓存方式 | 存储位置 | 容量限制 | 持久化 | 应用场景 |
|---|---|---|---|---|
| HTTP缓存 | 浏览器缓存 | 依浏览器 | 所有静态资源 | |
| Service Worker | CacheStorage | 依浏览器 | PWA、离线应用 | |
| LocalStorage | 浏览器本地 | 5-10MB | 小文件、配置 | |
| SessionStorage | 浏览器本地 | 5-10MB | 会话期间数据 | |
| IndexedDB | 浏览器本地 | 50MB-数GB | 大量结构化数据 | |
| Memory Cache | 内存 | 小 | 短期缓存 | |
| Application Cache | 浏览器缓存 | 依浏览器 | 已废弃 |
通过HTTP响应头控制浏览器缓存行为,是最基础也是最重要的缓存方式。
浏览器请求资源
↓
检查本地缓存
↓
├─ 有缓存 → 检查是否过期
│ ↓
│ ├─ 未过期 → 直接使用(from disk cache)
│ │
│ └─ 已过期 → 协商缓存验证
│ ↓
│ ├─ 304 Not Modified → 使用缓存
│ └─ 200 OK → 下载新资源
│
└─ 无缓存 → 发起网络请求
// 服务器响应头
Expires: Wed, 21 Oct 2026 07:28:00 GMT
// 问题:依赖客户端时间,可能不准确// 常用指令
Cache-Control: max-age=31536000 // 缓存1年
Cache-Control: max-age=0 // 立即过期
Cache-Control: no-cache // 需要协商缓存
Cache-Control: no-store // 完全不缓存
Cache-Control: public // 可被任何缓存
Cache-Control: private // 仅浏览器缓存
Cache-Control: immutable // 内容不可变
Cache-Control: must-revalidate // 过期必须验证
// 组合使用
Cache-Control: public, max-age=31536000, immutable// 首次请求 - 服务器响应
HTTP/1.1 200 OK
Last-Modified: Tue, 12 Jan 2026 09:00:00 GMT
Cache-Control: no-cache
// 再次请求 - 客户端发送
GET /style.css HTTP/1.1
If-Modified-Since: Tue, 12 Jan 2026 09:00:00 GMT
// 服务器响应
// 未修改:304 Not Modified(不返回内容)
// 已修改:200 OK(返回新内容)// 首次请求 - 服务器响应
HTTP/1.1 200 OK
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: no-cache
// 再次请求 - 客户端发送
GET /script.js HTTP/1.1
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// 服务器比对ETag
// 相同:304 Not Modified
// 不同:200 OK(返回新内容)
ETag vs Last-Modifiedserver {
listen 80;
server_name example.com;
# HTML文件 - 协商缓存
location ~* \.html$ {
add_header Cache-Control "no-cache";
etag on;
}
# CSS/JS - 强缓存(带版本号)
location ~* \.(css|js)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
etag on;
}
# 图片 - 强缓存
location ~* \.(jpg|jpeg|png|gif|svg|webp)$ {
add_header Cache-Control "public, max-age=2592000"; # 30天
}
# 字体文件 - 强缓存
location ~* \.(woff|woff2|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
}
}const express = require('express');
const app = express();
// 静态资源缓存
app.use('/static', express.static('public', {
maxAge: '1y', // 缓存1年
immutable: true, // 标记为不可变
etag: true, // 启用ETag
lastModified: true // 启用Last-Modified
}));
// HTML文件不缓存
app.get('*.html', (req, res) => {
res.setHeader('Cache-Control', 'no-cache');
res.sendFile(__dirname + req.path);
});
// API接口不缓存
app.use('/api', (req, res, next) => {
res.setHeader('Cache-Control', 'no-store');
next();
});Service Worker是运行在浏览器后台的脚本,可以拦截网络请求,实现强大的缓存策略,是PWA的核心技术。
优点
// main.js - 注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log(' SW注册成功:', registration.scope);
})
.catch(error => {
console.error(' SW注册失败:', error);
});
});
}// sw.js - Service Worker文件
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js',
'/images/logo.png'
];
// 安装事件 - 缓存资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log(' 开始缓存文件');
return cache.addAll(urlsToCache);
})
);
});
// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log(' 删除旧缓存:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
// 拦截请求 - 返回缓存
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 缓存命中,返回缓存
if (response) {
return response;
}
// 缓存未命中,发起网络请求
return fetch(event.request);
})
);
});// 适用场景:静态资源,如CSS、JS、图片
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});// 适用场景:API接口、实时数据
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
// 请求成功,更新缓存
const cloned = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, cloned);
});
return response;
})
.catch(() => {
// 网络失败,返回缓存
return caches.match(event.request);
})
);
});// 适用场景:平衡性能和新鲜度
self.addEventListener('fetch', event => {
event.respondWith(
caches.open(CACHE_NAME).then(cache => {
return cache.match(event.request).then(cached => {
// 立即返回缓存
const fetchPromise = fetch(event.request).then(response => {
// 后台更新缓存
cache.put(event.request, response.clone());
return response;
});
// 返回缓存或网络请求(哪个先完成)
return cached || fetchPromise;
});
})
);
});// 适用场景:离线应用的核心资源
self.addEventListener('fetch', event => {
event.respondWith(caches.match(event.request));
});// 适用场景:不需要缓存的请求
self.addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});// sw.js - 智能缓存策略
const CACHE_NAME = 'v2';
const STATIC_CACHE = 'static-v2';
const DYNAMIC_CACHE = 'dynamic-v2';
// 静态资源列表
const STATIC_FILES = [
'/',
'/index.html',
'/css/app.css',
'/js/app.js',
'/images/logo.png'
];
// 安装
self.addEventListener('install', event => {
event.waitUntil(
caches.open(STATIC_CACHE)
.then(cache => cache.addAll(STATIC_FILES))
);
self.skipWaiting(); // 立即激活
});
// 激活
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys.filter(key => key !== STATIC_CACHE && key !== DYNAMIC_CACHE)
.map(key => caches.delete(key))
);
})
);
self.clients.claim(); // 立即控制页面
});
// 拦截请求 - 智能策略
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// 静态资源:Cache First
if (STATIC_FILES.includes(url.pathname)) {
event.respondWith(cacheFirst(request));
}
// API接口:Network First
else if (url.pathname.startsWith('/api/')) {
event.respondWith(networkFirst(request));
}
// 图片:Stale While Revalidate
else if (request.destination === 'image') {
event.respondWith(staleWhileRevalidate(request));
}
// 其他:Network Only
else {
event.respondWith(fetch(request));
}
});
// Cache First策略
async function cacheFirst(request) {
const cached = await caches.match(request);
return cached || fetch(request);
}
// Network First策略
async function networkFirst(request) {
const cache = await caches.open(DYNAMIC_CACHE);
try {
const response = await fetch(request);
cache.put(request, response.clone());
return response;
} catch (error) {
return await caches.match(request);
}
}
// Stale While Revalidate策略
async function staleWhileRevalidate(request) {
const cache = await caches.open(DYNAMIC_CACHE);
const cached = await cache.match(request);
const fetchPromise = fetch(request).then(response => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}// 安装Workbox
npm install workbox-webpack-plugin --save-dev
// webpack.config.js
const { GenerateSW } = require('workbox-webpack-plugin');
module.exports = {
plugins: [
new GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
handler: 'CacheFirst',
options: {
cacheName: 'images',
expiration: {
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30天
}
}
},
{
urlPattern: /^https:\/\/api\.example\.com/,
handler: 'NetworkFirst',
options: {
cacheName: 'api',
networkTimeoutSeconds: 3
}
}
]
})
]
};
// 或直接在sw.js中使用
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');
workbox.routing.registerRoute(
({ request }) => request.destination === 'image',
new workbox.strategies.CacheFirst()
);
workbox.routing.registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new workbox.strategies.NetworkFirst()
);| 特性 | LocalStorage | SessionStorage |
|---|---|---|
| 生命周期 | 永久(除非手动删除) | 标签页关闭即清除 |
| 作用域 | 同源所有窗口共享 | 仅当前标签页 |
| 容量 | 5-10MB | 5-10MB |
| 存储类型 | 字符串 | 字符串 |
// 存储
localStorage.setItem('key', 'value');
sessionStorage.setItem('key', 'value');
// 读取
const value = localStorage.getItem('key');
// 删除
localStorage.removeItem('key');
// 清空
localStorage.clear();
// 获取键名
const key = localStorage.key(0);
// 存储对象(需JSON序列化)
const user = { name: 'John', age: 30 };
localStorage.setItem('user', JSON.stringify(user));
const savedUser = JSON.parse(localStorage.getItem('user'));// 缓存工具类
class LocalCache {
// 设置缓存(带过期时间)
static set(key, value, expires = 0) {
const item = {
value: value,
timestamp: Date.now(),
expires: expires // 毫秒
};
localStorage.setItem(key, JSON.stringify(item));
}
// 获取缓存
static get(key) {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
// 检查是否过期
if (item.expires && Date.now() - item.timestamp > item.expires) {
this.remove(key);
return null;
}
return item.value;
}
// 删除缓存
static remove(key) {
localStorage.removeItem(key);
}
// 清空所有缓存
static clear() {
localStorage.clear();
}
}
// 使用示例
// 缓存CSS文本(1天过期)
LocalCache.set('app-css', cssText, 24 * 60 * 60 * 1000);
// 读取缓存
const cachedCSS = LocalCache.get('app-css');
if (cachedCSS) {
const style = document.createElement('style');
style.textContent = cachedCSS;
document.head.appendChild(style);
} else {
// 加载CSS
fetch('/app.css').then(res => res.text()).then(css => {
LocalCache.set('app-css', css, 24 * 60 * 60 * 1000);
});
}// 图片缓存工具
async function cacheImage(url, key) {
// 检查缓存
const cached = LocalCache.get(key);
if (cached) {
return cached;
}
// 下载并转Base64
const response = await fetch(url);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64 = reader.result;
// 缓存7天
LocalCache.set(key, base64, 7 * 24 * 60 * 60 * 1000);
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// 使用
const imgElement = document.getElementById('logo');
cacheImage('/images/logo.png', 'logo-img').then(base64 => {
imgElement.src = base64;
});
注意事项IndexedDB是浏览器提供的NoSQL数据库,支持存储大量结构化数据和文件。
优点
// 打开数据库
const openDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyCache', 1);
// 创建对象存储(首次或升级时)
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('resources')) {
const store = db.createObjectStore('resources', { keyPath: 'url' });
store.createIndex('timestamp', 'timestamp', { unique: false });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
// 存储资源
async function saveResource(url, data, type) {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['resources'], 'readwrite');
const store = transaction.objectStore('resources');
const item = {
url: url,
data: data,
type: type,
timestamp: Date.now()
};
const request = store.put(item);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
// 读取资源
async function getResource(url) {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['resources'], 'readonly');
const store = transaction.objectStore('resources');
const request = store.get(url);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// 删除资源
async function deleteResource(url) {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['resources'], 'readwrite');
const store = transaction.objectStore('resources');
const request = store.delete(url);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}// 安装
npm install localforage
// 使用
import localforage from 'localforage';
// 配置
localforage.config({
driver: localforage.INDEXEDDB,
name: 'MyApp',
storeName: 'resources',
version: 1.0
});
// 存储
await localforage.setItem('logo', imageBlob);
await localforage.setItem('config', { theme: 'dark' });
// 读取
const logo = await localforage.getItem('logo');
const config = await localforage.getItem('config');
// 删除
await localforage.removeItem('logo');
// 清空
await localforage.clear();
// 获取所有键
const keys = await localforage.keys();
// 遍历
await localforage.iterate((value, key) => {
console.log(key, value);
});// 图片缓存管理器
class ImageCacheManager {
constructor() {
this.db = localforage.createInstance({
name: 'ImageCache'
});
}
// 缓存图片
async cacheImage(url) {
// 检查缓存
const cached = await this.db.getItem(url);
if (cached) {
return URL.createObjectURL(cached.blob);
}
// 下载图片
const response = await fetch(url);
const blob = await response.blob();
// 存储
await this.db.setItem(url, {
blob: blob,
timestamp: Date.now(),
size: blob.size
});
return URL.createObjectURL(blob);
}
// 获取缓存大小
async getCacheSize() {
let totalSize = 0;
await this.db.iterate((value) => {
totalSize += value.size || 0;
});
return totalSize;
}
// 清理过期缓存(7天)
async cleanExpired() {
const expireTime = 7 * 24 * 60 * 60 * 1000;
const now = Date.now();
const keysToDelete = [];
await this.db.iterate((value, key) => {
if (now - value.timestamp > expireTime) {
keysToDelete.push(key);
}
});
for (const key of keysToDelete) {
await this.db.removeItem(key);
}
return keysToDelete.length;
}
// 限制缓存大小(100MB)
async limitCacheSize(maxSize = 100 * 1024 * 1024) {
const items = [];
await this.db.iterate((value, key) => {
items.push({ key, value });
});
// 按时间排序(旧的优先删除)
items.sort((a, b) => a.value.timestamp - b.value.timestamp);
let totalSize = items.reduce((sum, item) => sum + item.value.size, 0);
for (const item of items) {
if (totalSize <= maxSize) break;
await this.db.removeItem(item.key);
totalSize -= item.value.size;
}
}
}
// 使用
const imageCache = new ImageCacheManager();
// 显示图片
async function displayImage(imageUrl) {
const img = document.createElement('img');
img.src = await imageCache.cacheImage(imageUrl);
document.body.appendChild(img);
}
// 定期清理
setInterval(async () => {
const deleted = await imageCache.cleanExpired();
console.log(`清理了${deleted}个过期缓存`);
await imageCache.limitCacheSize();
}, 24 * 60 * 60 * 1000); // 每天执行浏览器自动管理的内存缓存,速度最快但生命周期短(标签页关闭即清除)。
// 内存缓存通常缓存以下资源:
// 1. 页面中已经加载过的图片
// 2. 预加载的资源
<link rel="preload" href="script.js" as="script">
// 3. Base64图片
<img src="data:image/png;base64,...">
// 4. 小文件(通常<200KB)// 简单的内存缓存实现
class MemoryCache {
constructor(maxSize = 50 * 1024 * 1024) { // 50MB
this.cache = new Map();
this.maxSize = maxSize;
this.currentSize = 0;
}
set(key, value) {
const size = this.calculateSize(value);
// 容量检查
while (this.currentSize + size > this.maxSize && this.cache.size > 0) {
// LRU: 删除最早的项
const firstKey = this.cache.keys().next().value;
this.delete(firstKey);
}
this.cache.set(key, {
value,
size,
timestamp: Date.now()
});
this.currentSize += size;
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// 更新访问时间(LRU)
this.cache.delete(key);
this.cache.set(key, item);
return item.value;
}
delete(key) {
const item = this.cache.get(key);
if (item) {
this.currentSize -= item.size;
this.cache.delete(key);
}
}
clear() {
this.cache.clear();
this.currentSize = 0;
}
calculateSize(value) {
if (typeof value === 'string') {
return value.length * 2; // UTF-16
}
if (value instanceof Blob) {
return value.size;
}
return JSON.stringify(value).length * 2;
}
}
// 使用
const memCache = new MemoryCache();
memCache.set('user-data', userData);
const cached = memCache.get('user-data');请求资源流程:
1. Memory Cache(内存)
├─ 命中 → 返回(最快)
└─ 未命中 ↓
2. Service Worker Cache
├─ 命中 → 返回(快)
└─ 未命中 ↓
3. HTTP Cache(Disk Cache)
├─ 命中 → 返回(较快)
└─ 未命中 ↓
4. IndexedDB / LocalStorage
├─ 命中 → 返回(中等)
└─ 未命中 ↓
5. Network(网络请求)
└─ 下载资源(慢)
// 综合缓存管理
class UnifiedCacheManager {
constructor() {
this.memoryCache = new Map();
this.idbCache = localforage.createInstance({
name: 'UnifiedCache'
});
}
// 获取资源(多层查找)
async get(url, options = {}) {
const { maxAge = 0 } = options;
// 1. 检查内存缓存
const memCached = this.memoryCache.get(url);
if (memCached && this.isValid(memCached, maxAge)) {
console.log(' Memory Cache命中:', url);
return memCached.data;
}
// 2. 检查Service Worker缓存
if ('caches' in window) {
const swCached = await caches.match(url);
if (swCached) {
console.log(' SW Cache命中:', url);
const data = await swCached.text();
this.setMemoryCache(url, data);
return data;
}
}
// 3. 检查IndexedDB
const idbCached = await this.idbCache.getItem(url);
if (idbCached && this.isValid(idbCached, maxAge)) {
console.log(' IndexedDB命中:', url);
this.setMemoryCache(url, idbCached.data);
return idbCached.data;
}
// 4. 网络请求
console.log(' 网络请求:', url);
const response = await fetch(url);
const data = await response.text();
// 缓存到各层
await this.setAll(url, data);
return data;
}
// 设置所有层缓存
async setAll(url, data) {
// 内存缓存
this.setMemoryCache(url, data);
// IndexedDB缓存
await this.idbCache.setItem(url, {
data,
timestamp: Date.now()
});
// Service Worker缓存
if ('caches' in window) {
const cache = await caches.open('unified-cache-v1');
await cache.put(url, new Response(data));
}
}
// 内存缓存
setMemoryCache(url, data) {
this.memoryCache.set(url, {
data,
timestamp: Date.now()
});
}
// 检查有效性
isValid(cached, maxAge) {
if (maxAge === 0) return true;
return Date.now() - cached.timestamp < maxAge;
}
// 清除所有缓存
async clearAll() {
this.memoryCache.clear();
await this.idbCache.clear();
if ('caches' in window) {
const names = await caches.keys();
await Promise.all(names.map(name => caches.delete(name)));
}
}
}
// 使用
const cacheManager = new UnifiedCacheManager();
// 获取资源(30天有效期)
const css = await cacheManager.get('/app.css', {
maxAge: 30 * 24 * 60 * 60 * 1000
});
// 应用CSS
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);| 资源类型 | 推荐策略 | Cache-Control | 原因 |
|---|---|---|---|
| HTML文件 | 协商缓存 | no-cache | 需要及时更新 |
| CSS/JS(带hash) | 强缓存 | max-age=31536000, immutable | 内容不变 |
| 图片 | 强缓存 | max-age=2592000 | 变化少,30天 |
| 字体文件 | 强缓存 | max-age=31536000 | 基本不变 |
| API接口 | 不缓存 | no-store | 实时数据 |
| 视频 | 强缓存 | max-age=604800 | 大文件,7天 |
// Webpack配置 - 文件名hash
module.exports = {
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js'
}
};
// 生成的文件
main.a1b2c3d4.js
vendor.e5f6g7h8.js
// HTML中引用
<script src="/static/js/main.a1b2c3d4.js"></script>
// 优势:文件内容变化时hash变化,自动更新// 1. 版本号更新
const CACHE_VERSION = 'v2';
const CACHE_NAME = `app-cache-${CACHE_VERSION}`;
// 2. 时间戳更新
<script src="/app.js?t=1672531200000"></script>
// 3. Git commit hash
<link href="/style.css?v=abc123f" rel="stylesheet">
// 4. Service Worker强制更新
self.addEventListener('install', event => {
self.skipWaiting(); // 跳过等待,立即激活
});
self.addEventListener('activate', event => {
self.clients.claim(); // 立即控制所有页面
});现象:更新了文件但用户看到的还是旧版本
解决:
app.abc123.jsCache-Control: no-cache解决:
// 定期清理IndexedDB
async function cleanOldCache() {
const maxAge = 30 * 24 * 60 * 60 * 1000; // 30天
const now = Date.now();
await localforage.iterate((value, key) => {
if (now - value.timestamp > maxAge) {
localforage.removeItem(key);
}
});
}
// Service Worker限制缓存数量
workbox.expiration.ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 30 * 24 * 60 * 60
});
解决:
// 服务器添加CORS头
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Max-Age: 86400
// fetch请求指定模式
fetch(url, {
mode: 'cors',
credentials: 'omit'
});// Performance API监控
const cacheStats = {
total: 0,
fromCache: 0,
fromNetwork: 0
};
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
cacheStats.total++;
if (entry.transferSize === 0) {
// 来自缓存
cacheStats.fromCache++;
} else {
// 来自网络
cacheStats.fromNetwork++;
}
}
console.log('缓存命中率:',
(cacheStats.fromCache / cacheStats.total * 100).toFixed(2) + '%'
);
});
observer.observe({ entryTypes: ['resource'] });// 查看Storage使用情况
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(estimate => {
const used = (estimate.usage / 1024 / 1024).toFixed(2);
const quota = (estimate.quota / 1024 / 1024).toFixed(2);
const percent = (estimate.usage / estimate.quota * 100).toFixed(2);
console.log(`已使用: ${used}MB / ${quota}MB (${percent}%)`);
});
}
// IndexedDB大小
async function getIndexedDBSize() {
let totalSize = 0;
await localforage.iterate((value) => {
const size = new Blob([JSON.stringify(value)]).size;
totalSize += size;
});
return (totalSize / 1024 / 1024).toFixed(2) + 'MB';
}// 1. index.html - 协商缓存
// Nginx配置
location = /index.html {
add_header Cache-Control "no-cache";
etag on;
}
// 2. 静态资源 - 强缓存(Webpack打包带hash)
// webpack.config.js
output: {
filename: 'js/[name].[contenthash:8].js',
publicPath: '/static/'
}
// Nginx配置
location /static/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
// 3. Service Worker - 离线支持
// sw.js
const CACHE_NAME = 'spa-v1';
// 预缓存核心资源
const PRECACHE_URLS = [
'/',
'/static/js/main.abc123.js',
'/static/css/app.def456.css'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(PRECACHE_URLS);
})
);
});
// API接口 - Network First
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
}
});
// 4. 图片 - IndexedDB缓存
// 使用前面的ImageCacheManager
const imageCache = new ImageCacheManager();
document.querySelectorAll('img[data-src]').forEach(async img => {
const url = img.dataset.src;
img.src = await imageCache.cacheImage(url);
});| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 首次加载 | 3.2s | 3.0s | -6% |
| 二次加载 | 1.8s | 0.3s | -83% |
| 离线可用 | - | ||
| 流量消耗 | 2.5MB | 0.1MB | -96% |
暂无评论,快来发表第一条评论吧~