Cache strategy and offline resource management
Basic Concepts of Caching Strategies
Caching strategies are one of the core methods for optimizing web application performance. HTML5 provides various caching mechanisms, primarily including Service Worker, Cache API, Application Cache (deprecated), and others. Proper caching strategies can significantly improve page load speed, reduce server load, and provide basic functionality in offline states.
Browser caching is typically categorized into the following types:
- Strong caching: Implemented via
Expires
orCache-Control
headers - Negotiation caching: Implemented via
Last-Modified
/If-Modified-Since
orETag
/If-None-Match
- Application caching: Implemented via Service Worker and Cache API
// Check if the browser supports Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('ServiceWorker registration successful:', registration.scope);
})
.catch(err => {
console.log('ServiceWorker registration failed:', err);
});
}
Service Worker Caching Mechanism
Service Worker is a script that runs in the browser background, independent of the web page thread, and can intercept and process network requests. It forms the technical foundation of Progressive Web Apps (PWA).
The typical Service Worker lifecycle includes:
- Register
- Install
- Activate
- Idle
- Terminated
- Reactivated
// sw.js example
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
self.addEventListener('install', event => {
// Perform installation steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Cache opened');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
Cache API Detailed Explanation
The Cache API provides a mechanism for storing network request and response pairs and serves as the foundation for Service Worker caching functionality. It allows developers to create multiple named caches and perform CRUD operations on them.
Key methods include:
CacheStorage.open()
: Opens a cache with the specified nameCache.add()
: Fetches a URL resource and adds it to the cacheCache.addAll()
: Fetches multiple URL resources and adds them to the cacheCache.put()
: Stores a request/response pairCache.match()
: Finds the first matching item in the cacheCache.delete()
: Deletes a cache item
// Example using Cache API
caches.open('my-cache').then(cache => {
// Add a single resource
cache.add('/images/photo.jpg');
// Add multiple resources
cache.addAll([
'/styles/style.css',
'/scripts/app.js'
]);
// Custom request storage
const response = new Response('Hello world!');
cache.put('/greeting', response);
// Retrieve from cache
cache.match('/images/photo.jpg').then(response => {
if (response) {
// Use the cached response
}
});
});
Offline Resource Management Strategies
Effective offline resource management requires consideration of resource types, update frequency, and storage limits. Common strategies include:
- Cache-First Strategy: Load from cache first, fall back to network if failed
- Network-First Strategy: Request network first, fall back to cache if failed
- Cache-Only Strategy: Load only from cache
- Network-Only Strategy: Load only from network
- Cache-and-Update Strategy: Quickly display cached content while updating in the background
// Cache-First Strategy implementation
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit
if (response) {
return response;
}
// No cache, request network
return fetch(event.request).then(
response => {
// Check if response is valid
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone response
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
Cache Version Control and Updates
Cache resources require version control to ensure users receive the latest content. Common practices include:
- Include version numbers in cache names
- Use content hashes as filenames
- Clean up old caches during the Service Worker activation phase
// Cache version control example
const CACHE_VERSION = 2;
const CURRENT_CACHES = {
static: `static-cache-v${CACHE_VERSION}`,
dynamic: `dynamic-cache-v${CACHE_VERSION}`
};
self.addEventListener('activate', event => {
// Delete old caches
const expectedCacheNames = Object.values(CURRENT_CACHES);
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!expectedCacheNames.includes(cacheName)) {
console.log('Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
Storage Quota and Cleanup Strategies
Browsers impose limits on cache storage, requiring proper management:
- Check available quota
- Set reasonable cache expiration times
- Implement LRU (Least Recently Used) algorithm to clean old caches
// Check storage quota
navigator.storage.estimate().then(estimate => {
console.log(`Used: ${estimate.usage} bytes`);
console.log(`Quota: ${estimate.quota} bytes`);
console.log(`Usage ratio: ${(estimate.usage / estimate.quota * 100).toFixed(2)}%`);
});
// LRU cache cleanup implementation
const MAX_ITEMS = 50;
caches.open('my-cache').then(cache => {
cache.keys().then(keys => {
if (keys.length > MAX_ITEMS) {
cache.delete(keys[0]).then(() => {
console.log('Cleaned oldest cache item');
});
}
});
});
Advanced Caching Patterns
Complex applications may require combining multiple caching strategies:
- Runtime Caching: Dynamically cache API responses
- Precaching: Cache critical resources in advance
- Fallback Caching: Provide fallback content for failed requests
- Incremental Caching: Cache only changed portions
// Runtime caching API responses example
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
caches.open('api-cache').then(cache => {
return fetch(event.request).then(response => {
// Only cache successful API responses
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
}).catch(() => {
// Return cached data if network fails
return cache.match(event.request);
});
})
);
}
});
// Fallback caching example
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
.catch(() => caches.match('/offline.html'))
);
});
Performance Optimization Practices
Specific methods to enhance performance with caching strategies:
- Preload Critical Resources: Use
<link rel="preload">
to load in advance - Lazy Load Non-Critical Resources: Load large files like images on demand
- Data Prefetching: Predict user behavior to fetch data in advance
- Cache Partitioning: Use different caches for different resource types
<!-- Preload critical resources -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="app.js" as="script">
<!-- Lazy load images -->
<img data-src="image.jpg" class="lazyload" alt="">
<script>
document.addEventListener("DOMContentLoaded", function() {
const lazyImages = [].slice.call(document.querySelectorAll("img.lazyload"));
if ("IntersectionObserver" in window) {
const lazyImageObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
const lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove("lazyload");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
}
});
</script>
Debugging and Monitoring
Effective debugging and monitoring methods:
- Chrome DevTools Application panel
- Use
navigator.storage
API to monitor storage usage - Implement cache hit rate statistics
- Error logging
// Cache hit rate statistics
let cacheHits = 0;
let totalRequests = 0;
self.addEventListener('fetch', event => {
totalRequests++;
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
cacheHits++;
console.log(`Cache hit rate: ${(cacheHits/totalRequests*100).toFixed(1)}%`);
return response;
}
return fetch(event.request);
})
);
});
// Error logging
self.addEventListener('error', event => {
console.error('Service Worker error:', event.message);
// Send error logs to server
fetch('/log-error', {
method: 'POST',
body: JSON.stringify({
error: event.message,
stack: event.error.stack
})
});
});
Security Considerations
Security issues to note when implementing caching:
- Avoid caching sensitive data
- Properly handle HTTPS and CORS
- Prevent cache poisoning attacks
- Implement appropriate cache validation
// Secure caching example
self.addEventListener('fetch', event => {
// Do not cache API requests and sensitive URLs
if (event.request.url.includes('/api/') ||
event.request.url.includes('/admin/')) {
return fetch(event.request);
}
// Only cache same-origin requests
if (new URL(event.request.url).origin !== location.origin) {
return fetch(event.request);
}
// Normal cache handling
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
Practical Application Example
E-commerce website caching implementation example:
// E-commerce SW implementation
const CACHE_VERSION = 'ecommerce-v3';
const PRECACHE = [
'/',
'/index.html',
'/styles/main.min.css',
'/scripts/app.min.js',
'/images/logo.svg',
'/offline.html',
'/products/cached.json'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_VERSION)
.then(cache => cache.addAll(PRECACHE))
.then(() => self.skipWaiting())
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_VERSION) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
self.addEventListener('fetch', event => {
const requestUrl = new URL(event.request.url);
// API requests use network-first strategy
if (requestUrl.pathname.startsWith('/api/')) {
event.respondWith(
fetch(event.request)
.then(response => {
// Only cache GET requests with successful responses
if (event.request.method === 'GET' && response.status === 200) {
const clone = response.clone();
caches.open(`${CACHE_VERSION}-dynamic`)
.then(cache => cache.put(event.request, clone));
}
return response;
})
.catch(() => {
// Try returning cache if network fails
return caches.match(event.request);
})
);
return;
}
// Static resources use cache-first strategy
event.respondWith(
caches.match(event.request)
.then(response => {
// If cache is found, return it
if (response) {
// Update cache in background
fetchAndCache(event.request);
return response;
}
// Otherwise request network
return fetchAndCache(event.request)
.catch(() => {
// If that fails, return offline page
if (event.request.mode === 'navigate') {
return caches.match('/offline.html');
}
});
})
);
});
function fetchAndCache(request) {
return fetch(request).then(response => {
// Check if response is valid
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// Clone response
const responseToCache = response.clone();
caches.open(CACHE_VERSION)
.then(cache => {
cache.put(request, responseToCache);
});
return response;
});
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:数据同步与冲突解决