阿里云主机折上折
  • 微信号
Current Site:Index > Cache strategy and offline resource management

Cache strategy and offline resource management

Author:Chuan Chen 阅读数:32502人阅读 分类: HTML

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 or Cache-Control headers
  • Negotiation caching: Implemented via Last-Modified/If-Modified-Since or ETag/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:

  1. Register
  2. Install
  3. Activate
  4. Idle
  5. Terminated
  6. 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 name
  • Cache.add(): Fetches a URL resource and adds it to the cache
  • Cache.addAll(): Fetches multiple URL resources and adds them to the cache
  • Cache.put(): Stores a request/response pair
  • Cache.match(): Finds the first matching item in the cache
  • Cache.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:

  1. Cache-First Strategy: Load from cache first, fall back to network if failed
  2. Network-First Strategy: Request network first, fall back to cache if failed
  3. Cache-Only Strategy: Load only from cache
  4. Network-Only Strategy: Load only from network
  5. 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:

  1. Include version numbers in cache names
  2. Use content hashes as filenames
  3. 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:

  1. Check available quota
  2. Set reasonable cache expiration times
  3. 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:

  1. Runtime Caching: Dynamically cache API responses
  2. Precaching: Cache critical resources in advance
  3. Fallback Caching: Provide fallback content for failed requests
  4. 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:

  1. Preload Critical Resources: Use <link rel="preload"> to load in advance
  2. Lazy Load Non-Critical Resources: Load large files like images on demand
  3. Data Prefetching: Predict user behavior to fetch data in advance
  4. 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:

  1. Chrome DevTools Application panel
  2. Use navigator.storage API to monitor storage usage
  3. Implement cache hit rate statistics
  4. 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:

  1. Avoid caching sensitive data
  2. Properly handle HTTPS and CORS
  3. Prevent cache poisoning attacks
  4. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.