Appearance
特点
不能操作 / 访问 DOM
会自动休眠,不会随浏览器关闭而失效,必须手动卸载
离线缓存内容开发者可控
必须在 https 或者 localhost 下使用
所有 api 都基于 promise
缓存策略
cachefirst
缓存优先
cacheonly
仅缓存
networkfirst
网络优先
networkonly
仅网络
StaleWhileRevalidate
从缓存取,用网络数据更新缓存。
实现
缓存数组中保存着需要缓存的请求资源路径,service worker 安装时,通过 const c = await caches.open(<name>) 创建一个缓存空间,然后 c.addAll(chcheList) 缓存这些请求资源路径返回的内容。通过 e.waitUntil(onCache()) 等待这个 promise 完成, 完成后再通过 e.waitUntil(skipWaiting()) 跳过等待。
service worker 激活时,service worker 并不是立即生效的,而是下一次访问才生效,我们 通过 e.waitUntil(Promise.all([clearCache(), clients.claim()])) 让 service worker 立即生效,同时清除之前的缓存。clearCache 中通过 await caches.keys(<name>) 获取所有 缓存空间的名字,遍历它们,将名字和当前名字不相等的通过 await caches.delete(key) 删除。
对请求进行拦截,如果请求的是数据会变化的接口,直接通过 fetch 发请求获取最新的数据,获取 到后克隆一份保存进行缓存中,然后将结果传入 e.respondWith 中。如果请求失败进入 catch, catch 中根据 e.request 从缓存中读取数据。
js
// 为了性能,在资源加载完毕后注册。
window.addEventListener('load', async () => {
if (navigator.serviceWorker) {
try {
// 每次 sw.js 变化都会重新注册一个 service worker
const registration = await navigator.serviceWorker.register('/sw.js')
} catch (error) {
console.log(error)
}
}
})js
// sw,js
const version = '0' // 缓存名
const cacheList = [ // 缓存列表
'/',
'/api/list',
'/index.css',
'/manifest.json',
'/logo.png'
]
// service worker 安装时创建缓存
const onCache = async () => {
// 创建一个缓存空间
const c = await caches.open(version)
// 缓存缓存列表的数据
await c.addAll(cacheList)
// 跳过等待
await self.skipWaiting()
}
// service worker 激活时清除之前的缓存
const clearCache = async () => {
// 获取缓存空间内的所有名字
const keys = await caches.keys(version)
for (let i = 0, l = keys.length; i < l; i++) {
// 删除之前的缓存
const key = keys[i]
if (key !== version) {
await caches.delete(key)
}
}
}
// 对于数据会变化的 API,先获取最新的再缓存。
const fetchAndSave = async (request) => {
const res = await fetch(request)
const cloned = res.clone() // 拷贝一份用于消费
const c = await caches.open(version)
// 更新
await c.put(request, cloned)
return res
}
// 拦截请求
self.addEventListener('fetch', e => {
const url = new URL(e.request.url)
// 静态资源,不做拦截。
if (url.origin !== self.origin) {
return
}
// 如果接口数据是变化的,将数据更新到缓存中。
if (e.request.url.includes('/api')) {
e.respondWith(fetchAndSave(e.request).catch(async () => {
const c = await caches.open(version)
return c.match(e.request)
}))
} else {
// service worker 不支持 ajax,因为线程中,不能发 ajax,支持 fetch。
// respondWith 表示用什么内容返回当前响应
e.respondWith(fetch(e.request).catch(async () => {
// 断网,去缓存中找。
const c = await caches.open(version)
return c.match(e.request)
}))
}
})
// service worker 安装时,跳过等待。
self.addEventListener('install', e => {
e.waitUntil(onCache())
e.waitUntil(skipWaiting()) // 等待 promise 执行完成
})
// // service worker 不是立即生效,下一次访问时才生效。
self.addEventListener('activate', e => {
// 激活后立刻让 service worker 生效。
e.waitUntil(Promise.all([clearCache(), clients.claim()]))
})