随着移动端应用的普及,PWA (Progressive Web App) 成为了越来越重要的技术选型。PWA 可以让 Web 应用更加接近原生应用的体验,并且具有离线可访问、推送通知等特性。其中,离线可访问是 PWA 最重要的特性之一,而缓存淘汰策略则是离线可访问的核心之一。本文将介绍 PWA 如何实现缓存淘汰策略,并提供示例代码。
什么是缓存淘汰策略?
在 PWA 中,我们可以使用 Service Worker 来实现缓存。Service Worker 是一个 JavaScript 文件,可以拦截网络请求并返回缓存中的响应。当用户访问一个页面时,Service Worker 会先在缓存中查找对应的响应,如果找到了就直接返回,否则再向服务器请求。
但是,缓存是有限的,如果缓存中存储的内容过多,就会占用过多的空间,从而影响用户的体验。因此,我们需要实现一种缓存淘汰策略,来保证缓存中存储的内容是最有价值的内容。
缓存淘汰策略的核心思想是根据一定的规则,将缓存中的内容进行优先级排序,然后淘汰掉优先级最低的内容。常见的缓存淘汰策略有三种:先进先出 (FIFO)、最近最少使用 (LRU) 和最不经常使用 (LFU)。
如何实现缓存淘汰策略?
FIFO
先进先出 (FIFO) 策略是最简单的缓存淘汰策略。它的核心思想是,先进入缓存的内容先被淘汰。具体实现可以使用数组来保存缓存的内容,每次新加入一个缓存内容时,将其放入数组的末尾,淘汰时则将数组的第一个元素弹出即可。
// javascriptcn.com 代码示例 const cache = []; const maxLength = 10; self.addEventListener('fetch', event => { const request = event.request; event.respondWith( caches.match(request).then(response => { if (response) { return response; } return fetch(request).then(response => { if (response.status === 200) { const cloneResponse = response.clone(); caches.open('cacheName').then(cache => { cache.put(request, cloneResponse); if (cache.length > maxLength) { cache.delete(cache.keys().next().value); } }); } return response; }); }) ); });
在上面的示例代码中,我们定义了一个数组 cache
来保存缓存内容,maxLength
表示缓存最大的长度。在每次新加入缓存内容时,我们先将其放入数组的末尾,然后判断数组的长度是否超过了最大长度,如果超过了就将数组的第一个元素弹出。
LRU
最近最少使用 (LRU) 策略是一种比较常用的缓存淘汰策略。它的核心思想是,淘汰掉最近最少使用的内容。具体实现可以使用双向链表来保存缓存的内容,每次新加入缓存内容时,将其放入链表的末尾。每次访问缓存内容时,将其移到链表的末尾。淘汰时则将链表头部的元素弹出即可。
// javascriptcn.com 代码示例 class Node { constructor(key, value) { this.key = key; this.value = value; this.prev = null; this.next = null; } } class LRUCache { constructor(capacity) { this.capacity = capacity; this.map = new Map(); this.head = new Node(); this.tail = new Node(); this.head.next = this.tail; this.tail.prev = this.head; } get(key) { if (this.map.has(key)) { const node = this.map.get(key); this.moveToTail(node); return node.value; } else { return null; } } put(key, value) { if (this.map.has(key)) { const node = this.map.get(key); node.value = value; this.moveToTail(node); } else { const node = new Node(key, value); this.map.set(key, node); this.addToTail(node); if (this.map.size > this.capacity) { const node = this.removeHead(); this.map.delete(node.key); } } } moveToTail(node) { this.removeNode(node); this.addToTail(node); } addToTail(node) { node.prev = this.tail.prev; node.next = this.tail; this.tail.prev.next = node; this.tail.prev = node; } removeNode(node) { node.prev.next = node.next; node.next.prev = node.prev; } removeHead() { const node = this.head.next; this.removeNode(node); return node; } } const cache = new LRUCache(10); self.addEventListener('fetch', event => { const request = event.request; event.respondWith( caches.match(request).then(response => { if (response) { return response; } return fetch(request).then(response => { if (response.status === 200) { const cloneResponse = response.clone(); caches.open('cacheName').then(cache => { cache.put(request, cloneResponse); cache.put(request.url, cloneResponse); }); cache.put(request.url, cloneResponse); } return response; }); }) ); });
在上面的示例代码中,我们定义了一个双向链表来保存缓存内容,capacity
表示缓存最大的容量。在每次新加入缓存内容时,我们先将其放入链表的末尾,然后判断链表的长度是否超过了最大容量,如果超过了就将链表头部的元素弹出。在每次访问缓存内容时,我们将其移到链表的末尾。
LFU
最不经常使用 (LFU) 策略是一种比较复杂的缓存淘汰策略。它的核心思想是,淘汰掉最不经常使用的内容。具体实现可以使用一个哈希表来保存缓存的内容,每次新加入缓存内容时,将其放入哈希表中,并将其访问次数设为 0。每次访问缓存内容时,将其访问次数加 1。淘汰时则将访问次数最少的内容淘汰掉。
// javascriptcn.com 代码示例 class LFUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); this.freqMap = new Map(); this.minFreq = 0; } get(key) { if (!this.cache.has(key)) { return null; } const value = this.cache.get(key).value; this.increaseFreq(key); return value; } put(key, value) { if (this.capacity === 0) { return; } if (this.cache.has(key)) { const node = this.cache.get(key); node.value = value; this.increaseFreq(key); } else { if (this.cache.size === this.capacity) { const minFreqList = this.freqMap.get(this.minFreq); const deleteNode = minFreqList.deleteHead(); this.cache.delete(deleteNode.key); } const node = new Node(key, value, 1); this.cache.set(key, node); const freqList = this.freqMap.get(1) || new DoublyLinkedList(); freqList.addToTail(node); this.freqMap.set(1, freqList); this.minFreq = 1; } } increaseFreq(key) { const node = this.cache.get(key); const freqList = this.freqMap.get(node.freq); freqList.removeNode(node); if (node.freq === this.minFreq && freqList.size === 0) { this.minFreq++; } node.freq++; const newFreqList = this.freqMap.get(node.freq) || new DoublyLinkedList(); newFreqList.addToTail(node); this.freqMap.set(node.freq, newFreqList); } } class Node { constructor(key, value, freq) { this.key = key; this.value = value; this.freq = freq; this.prev = null; this.next = null; } } class DoublyLinkedList { constructor() { this.head = new Node(); this.tail = new Node(); this.head.next = this.tail; this.tail.prev = this.head; this.size = 0; } addToTail(node) { node.prev = this.tail.prev; node.next = this.tail; this.tail.prev.next = node; this.tail.prev = node; this.size++; } removeNode(node) { node.prev.next = node.next; node.next.prev = node.prev; this.size--; } deleteHead() { const node = this.head.next; this.removeNode(node); return node; } } const cache = new LFUCache(10); self.addEventListener('fetch', event => { const request = event.request; event.respondWith( caches.match(request).then(response => { if (response) { return response; } return fetch(request).then(response => { if (response.status === 200) { const cloneResponse = response.clone(); caches.open('cacheName').then(cache => { cache.put(request, cloneResponse); cache.put(request.url, cloneResponse); }); cache.put(request.url, cloneResponse); } return response; }); }) ); });
在上面的示例代码中,我们定义了一个哈希表来保存缓存内容,capacity
表示缓存最大的容量。在每次新加入缓存内容时,我们先将其放入哈希表中,并将其访问次数设为 0。每次访问缓存内容时,我们将其访问次数加 1。在淘汰时,我们先找到访问次数最少的内容,然后将其从哈希表中删除。
总结
缓存淘汰策略是 PWA 中非常重要的一个概念,它可以帮助我们提高缓存的效率,并且保证缓存中存储的内容是最有价值的内容。本文介绍了三种常见的缓存淘汰策略:先进先出 (FIFO)、最近最少使用 (LRU) 和最不经常使用 (LFU),并提供了相应的示例代码。在实际的开发中,我们可以根据具体的需求选择合适的缓存淘汰策略。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6556cce8d2f5e1655d12ab90