各位老铁,早上好!今天咱们来聊聊 Service Worker 这玩意儿,特别是它那让人又爱又恨的缓存控制。 话说,这 Service Worker 就像个守门大爷,站在你的 Web 应用前面,拦截所有的网络请求。 好处是显而易见的:离线访问、加速加载等等。 但有时候,这大爷太尽职尽责了,啥都往缓存里塞,结果你更新了网站,用户看到的还是老旧版本,这就尴尬了。所以,咱们得学会怎么驯服这大爷,让他乖乖地听咱们的,该走缓存走缓存,该走网络走网络。
一、Service Worker 的生命周期:大爷的一生
首先,得了解 Service Worker 的一生,它分为几个阶段:
- 注册 (Registration): 告诉浏览器,嘿,这里有个 Service Worker,你管着点。
- 安装 (Installation): 下载 Service Worker 脚本,缓存静态资源。
- 激活 (Activation): 清理旧的缓存,准备接管页面控制权。
- 空闲 (Idle): 等待事件触发,比如网络请求、推送等等。
- 终止 (Termination): 浏览器认为 Service Worker 不再需要,将其销毁。
了解了这些阶段,才能更好地理解缓存控制的时机。
二、缓存策略:大爷的脾气
Service Worker 提供了多种缓存策略,就像大爷有不同的脾气一样。 咱们得了解每种脾气,才能对症下药。
-
Cache First (先缓存):
- 优先从缓存中获取资源,如果缓存中没有,再去网络请求。
- 适用于静态资源,比如图片、CSS、JS 文件。
- 代码示例:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Not in cache - return fetch request
return fetch(event.request);
}
)
);
});
-
Network First (先网络):
- 优先从网络获取资源,如果网络请求失败,再去缓存中获取。
- 适用于经常更新的资源,比如 API 数据。
- 代码示例:
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two independent copies.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
})
.catch(() => {
return caches.match(event.request);
})
);
});
-
Cache Only (仅缓存):
- 只从缓存中获取资源,如果没有,就返回错误。
- 适用于完全离线的应用。
- 代码示例:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
}
return new Response(null, { status: 404 });
})
);
});
-
Network Only (仅网络):
- 只从网络获取资源,不使用缓存。
- 适用于需要最新数据的资源。
- 代码示例:
self.addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
-
Stale-While-Revalidate (过时数据,后台更新):
- 先从缓存中获取资源,同时发起网络请求更新缓存。
- 用户可以立即看到缓存中的数据,同时在后台更新数据,下次访问时就能看到最新的数据。
- 适用于对实时性要求不高,但又希望快速加载的场景。
- 代码示例:
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Even if the response is in the cache, we fetch it from the server
// and update the cache in the background.
const fetchPromise = fetch(event.request).then(
networkResponse => {
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
}
)
// Return the cached response if we have one, otherwise return the network response.
return response || fetchPromise;
}
)
);
});
三、绕过缓存:大爷也有打盹的时候
有时候,我们需要绕过缓存,强制从网络获取最新的数据。 比如,用户点击了“刷新”按钮,或者我们更新了 API 接口,需要立即生效。
-
手动更新缓存:
-
在 Service Worker 中,监听
message
事件,当收到特定消息时,更新缓存。 -
代码示例:
-
Service Worker:
-
self.addEventListener('message', event => {
if (event.data.action === 'skipWaiting') {
self.skipWaiting();
} else if (event.data.action === 'updateCache') {
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(event.data.urls);
});
}
});
* **页面 JavaScript:**
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
// Send message to update cache
registration.active.postMessage({
action: 'updateCache',
urls: ['/api/data.json']
});
});
}
-
使用
Cache-Control
HTTP 头部:- 在服务器端设置
Cache-Control
头部,告诉浏览器和 Service Worker 如何缓存资源。 - 常用的指令:
no-cache
: 强制每次都从服务器获取资源。max-age=xxx
: 设置缓存的最大时间,单位是秒。no-store
: 禁止缓存任何内容。
- 在服务器端设置
-
使用 URL 查询参数:
- 在 URL 中添加一个随机的查询参数,强制浏览器认为这是一个新的 URL,从而绕过缓存。
- 例如:
https://example.com/api/data.json?v=${Date.now()}
-
使用
fetch
的cache
选项:- 在
fetch
请求中,可以设置cache
选项来控制缓存行为。 - 常用的选项:
default
: 使用默认的缓存策略。no-store
: 不使用缓存。reload
: 强制从网络获取资源,并更新缓存。no-cache
: 强制从网络获取资源,不更新缓存。force-cache
: 强制从缓存获取资源。
- 代码示例:
- 在
fetch('/api/data.json', { cache: 'reload' })
.then(response => response.json())
.then(data => {
// 处理数据
});
四、高级技巧:大爷的私人订制
除了以上常用的方法,还有一些高级技巧,可以让你更精细地控制缓存。
-
使用
workbox
库:workbox
是 Google 官方提供的 Service Worker 工具库,可以简化缓存管理。- 它提供了各种常用的缓存策略,以及一些高级功能,比如路由、预缓存等等。
- 使用
workbox
可以大大减少 Service Worker 的代码量,提高开发效率。 - 代码示例:
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
// Cache CSS, JS, and images with a Cache First strategy
registerRoute(
({request}) => ['style', 'script', 'image'].includes(request.destination),
new CacheFirst({
cacheName: 'static-resources',
})
);
// Cache API requests with a Network First strategy
registerRoute(
({url}) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
})
);
// Use Stale-While-Revalidate for everything else
registerRoute(
() => true,
new StaleWhileRevalidate({
cacheName: 'everything-else',
})
);
-
动态路由:
- 根据不同的 URL,使用不同的缓存策略。
- 可以使用正则表达式、通配符等等来匹配 URL。
- 代码示例:
self.addEventListener('fetch', event => {
const url = event.request.url;
if (url.includes('/api/')) {
// Use Network First for API requests
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
} else if (url.endsWith('.jpg') || url.endsWith('.png')) {
// Use Cache First for images
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
} else {
// Use Cache First for other resources
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
}
});
-
预缓存:
- 在 Service Worker 安装阶段,提前缓存一些重要的资源。
- 可以提高首次加载速度,减少网络请求。
- 代码示例:
const PRECACHE_URLS = [
'/',
'/index.html',
'/style.css',
'/app.js',
'/image.png'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(PRECACHE_URLS))
);
});
五、调试技巧:和大爷好好沟通
调试 Service Worker 缓存问题,就像和大爷好好沟通一样,需要耐心和技巧。
-
Chrome DevTools:
- Chrome DevTools 提供了强大的 Service Worker 调试工具。
- 可以在 "Application" -> "Service Workers" 面板中,查看 Service Worker 的状态、缓存内容、网络请求等等。
- 可以模拟离线状态,强制更新 Service Worker,清除缓存等等。
-
控制台日志:
- 在 Service Worker 代码中添加
console.log
语句,可以方便地查看代码执行情况。 - 注意,Service Worker 的
console.log
语句会在 Chrome DevTools 的 "Console" 面板中显示。
- 在 Service Worker 代码中添加
-
错误处理:
- 在 Service Worker 代码中添加错误处理机制,可以捕获异常,避免 Service Worker 崩溃。
- 可以使用
try...catch
语句,或者Promise.catch
方法来捕获错误。
六、总结:驯服大爷,为我所用
Service Worker 的缓存控制是一个复杂而重要的主题。 掌握了这些技巧,你就能更好地驯服这只大爷,让它乖乖地为你所用,提升 Web 应用的性能和用户体验。 记住,灵活运用不同的缓存策略,根据实际情况进行调整,才能达到最佳效果。
最后,希望今天的讲座对大家有所帮助。 祝大家编程愉快,告辞!