外观
本篇简单介绍了 Service Worker 的基本概念,以及一些简单的使用场景。
祖传开篇:作者水平有限,可能比较简陋,也或许有些错误,欢迎指正。
什么是 Service Worker ?
Service Worker (MDN文档) 是一个注册在指定源和路径下的 事件驱动
worker (MDN文档)采用 JavaScript 文件的形式,控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。
特性
- 不能直接操作DOM
- 需要时会唤醒,不需要时会休眠,不会随着网页的关闭或浏览器的关闭而自动关闭。
- 缓存的内容开发者可以控制
- 安装后永远存活,除非手动卸载
- 必须由 HTTPS 承载
- 在 Service Worker 中,大量使用了 Promise
- 有自己的独立的生命周期
- 可以使用 indexDB 和 Cache API, 但是不能访问 localStorage
注册 Service Worker
使用 ServiceWorkerContainer.register(scriptURL, options)
方法首次注册 service worker。如果注册成功,service worker 就会被下载到客户端并尝试安装或激活(见下文),这将作用于整个域内用户可访问的 URL,或者其特定子集。
第一个参数 scriptURL
是一个 URL,它指向一个 Service Worker 脚本。
第二个参数 options
是一个可选对象,它包含以下属性: scope
表示定义 service worker 注册范围的 URL;service worker 可以控制的 URL 范围。通常是相对 URL。
js
if ('serviceWorker' in navigator) {
try {
const pageCache = await navigator.serviceWorker.register("/sw.js", {scope: '/'})
if (pageCache.installing) {
console.log("正在安装 Service worker");
} else if (pageCache.waiting) {
console.log("已安装 Service worker installed");
} else if (pageCache.active) {
console.log("激活 Service worker");
}
} catch (e) {
console.error('serviceWorker 注册失败: ' + e)
}
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('Service worker 更新了')
})
} else {
console.warn("当前浏览器不支持 serviceWorker")
}
生命周期
service worker 将遵守以下生命周期
下载
用户首次访问 service worker 控制的网站或页面时,service worker 会立刻被下载。
安装
service worker 下载完成后,会进入安装阶段,此时 service worker 会触发 install
事件,此时可以进行一些初始化操作,如缓存资源等。
激活
安装完成后,service worker 会进入激活阶段,此时 service worker 会触发 activate
事件,此时可以进行一些清理操作,如删除过期的缓存等。
通讯
在 Service Worker 安装完成后,它就可以与网页进行通讯了。
为了确保所有受控客户端都能接收到 Service Worker 激活的通知,通常会选择向每个客户端发送消息。如果你只需要通知特定的客户端,可以在逻辑上进行筛选,但默认情况下,通知所有客户端是一个更安全和全面的做法。
js
self.addEventListener('activate', (event) => {
event.waitUntil(
// 获取所有当前service worker控制的客户端(例如,打开的页面)
self.clients.matchAll().then((client) => {
// 向每个客户端发送消息,通知它们service worker已被激活并准备处理fetch请求
client.postMessage({
msg: 'Hey, from service worker! I\'m listening to your fetch requests.',
source: 'service-worker'
})
})
)
})
客户端需要部署消息监听代码:
js
navigator.serviceWorker.addEventListener('message', (event) => {
console.log('来自 Service Worker 的通讯:', event.data.message);
})
示例
简单的演示用法
拦截 fetch 请求
js
const CHANG_ROUTER = ['upload/page'] // 修改请求信息的请求
const Prohibited_Requests = ['upload/pageAll'] // 直接拦截的请求
self.addEventListener('fetch', event => {
// 如果请求url包含__api/,则修改请求信息,并返回fetch请求
if (event.request.url.includes('__api/')) {
// 简单获取一下请求路由
let urlRouter = event.request.url.split('__api/')[1];
// 如果请求路由包含在CHANG_ROUTER中,则修改请求信息,并返回fetch请求
if (CHANG_ROUTER.includes(urlRouter)) {
// 去掉 __api/ 保持原有信息继续请求
event.respondWith(fetch(event.request.url.replace('__api/', ''), {
method: event.request.method, body: event.request.body, headers: event.request.headers
}).then(response => {
return response;
}));
}
// 如果请求路由包含在Prohibited_Requests中,则拦截请求,并返回错误信息
else if (Prohibited_Requests.includes(urlRouter)) {
// 向所有客户端发送消息,通知它们请求被拦截
self.clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage({type: 'Interception', data: urlRouter, message: '该请求已被拦截'});
})
})
event.respondWith(new Response(JSON.stringify('请求被拦截'), {
status: 403,
statusText: 'Request Intercepted',
headers: {'Content-Type': 'application/json'}
}));
} else {
fetch(event.request).catch(() => {
// 当请求失败时返回离线页面
return caches.match(event.request).then(response => {
return response || caches.match(OFFLINE_URL);
});
})
}
}
})
缓存指定内容
在 Service Worker 中,缓存的机制是使用 caches
对象提供的方法来操作的。
在 public 文件夹下创建一个 offline.html 文件,作为离线页面。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>离线模式</title>
</head>
<body>
<h1>您已处于离线状态</h1>
<p>请检查您的网络连接。</p>
</body>
</html>
js
const CACHE_NAME = 'my-vue-cache-v1'
const OFFLINE_URL = '/offline.html'
// 缓存的静态资源
const CACHE_ASSETS = [
OFFLINE_URL
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(CACHE_ASSETS);
}).catch(err => console.error('缓存失败:', err))
)
// 跳过等待,立即激活
self.skipWaiting();
})
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames =>
Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log(`[Service Worker] 删除旧缓存: ${cacheName}`);
return caches.delete(cacheName);
}
})
)
)
)
self.clients.claim(); // 控制所有页面
})
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).catch(() => {
if (event.request.mode === 'navigate') {
// 如果是导航请求,返回离线页面
return caches.match(OFFLINE_URL);
} else {
// 非导航请求返回缓存内容(若存在)
return caches.match(event.request);
}
})
)
})
完成一次安装后,就可以在 浏览器的开发者控制面板中,打开应用程序 ,找到 缓存存储 就可以看到 以 my-vue-cache-v1 命名的缓存了。
然后离线状态下访问页面,就会显示 offline.html 的内容了
其他 API
相关 API 有很多,作者也是就了解一些,详细的 API 文档大家可以移步 MDN。
下面介绍一个比较有意思的 API
notificationclick
在主线程上或在非 Service Worker 的工作线程中创建的通知 使用 Notification() 构造函数将 而是在 Notification 对象上接收 Click 事件 本身。 例如:
js
function showNotification(title, body) {
if (Notification.permission === 'granted') {
new Notification(title, {body});
} else {
console.log('未授予通知权限');
}
}
onMounted(() => {
document.getElementById('request-permission-btn').addEventListener('click', () => {
// 请求通知权限
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
console.log('用户已授予通知权限');
showNotification('通知已启用', '您将收到重要通知。');
} else if (permission === 'denied') {
console.log('用户拒绝了通知权限');
} else {
console.log('用户未决定');
}
});
});
})
在 Service Worker 中触发 ServiceWorkerGlobalScope
接口的 notificationclick
事件,生成的系统通知。(同样也需要用户授予权限)
js
navigator.serviceWorker.addEventListener('message', (event) => {
console.log('来自 Service Worker 的通讯:', event.data.message);
navigator.serviceWorker.ready.then(registration => {
registration.showNotification('来自 Service Worker 的消息', {
body: event.data.message
})
})
})
在 Service Worker 中,监听 notificationclick
事件。
js
self.addEventListener('notificationclick', event => {
console.log('通知被点击:', event.notification.tag);
event.notification.close(); // 关闭通知
});
// 这里也可以使用 self.onnotificationclick = (event) => {})
Push API
这个API 是一个 Web API,它允许 Web 应用程序向用户发送推送消息。即使用户没有打开应用程序,也可以通过 Push API 发送推送消息。
在 Service Worker 中,监听 push
事件。
js
self.addEventListener('push', event => {
const data = event.data.json(); // 解析推送消息的内容
const title = data.title || '新消息';
const options = {
body: data.body || '你有一条新消息',
icon: '/icon.png',
badge: '/badge.png'
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
这个就简单演示一下吧,具体的使用大家可以参考 MDN。
使用时需要准备太多内容,大概如下:
- Service Worker: 用于接收推送消息。
- VAPID 密钥: 用于验证推送消息的来源。
- 客户端订阅: 使用 PushManager.subscribe() 订阅推送服务。
- 服务器配置: 使用 web-push 库发送推送消息。
- HTTPS 环境: 确保网站在 HTTPS 下运行。
- 浏览器兼容性: 检查目标浏览器的支持情况。