Service Worker 是一种在后台中运行的脚本,能够拦截和处理网络请求,并且可以实现离线功能和推送通知。
简单来说,Service Worker 可以让我们写的 Web 应用更像一个原生应用表现。
一、概述
Service Worker 的诞生伴随着 PWA 技术,是现代 Web 技术的重要组成部分。
PWA (让浏览器弹出将网站安装应用程序)组成分为三部分:
- Service Worker
- HTTPS 协议(本地开发时可以使用 HTTP)
- manifest.json(Web 应用程序的清单文件)
编写 PWA 应用,Service Worker 已经有越来越多的开发者所使用。
图来自 State of JavaScript 2023:https://share.stateofjs.com/share/prerendered?localeId=zh-Hans&surveyId=state_of_js&editionId=js2023&blockId=browser_api_features¶ms=§ionId=features
举两个可以完全离线使用的 Web 应用:
- https://squoosh.app/ – 压缩图片、转换图片格式
- https://excalidraw.com/ – 画图工具
还有其他知名的 Web 应用使用了 Service Worker:
更多 PWA 应用可以在 https://www.pwalist.app/ 寻找试试
二、主要功能以及架构图
- 离线支持:缓存资源,使应用在离线时也能运行。
- 性能优化:通过缓存静态资源,减少网络请求,提高加载速度。
- 后台同步:在网络连接恢复时同步数据。
- 推送通知:即使应用未打开,也能接收通知。
浏览器请求某一个资源时,会先调用 Service Worker,然后决定是发送一个新请求,还是从 Cache 当中存取。
三、生命周期
- Installing 阶段:sw 安装时,此时刚开始注册
- Installed 阶段:sw 设置完成,等待其他 sw(过期)完成关闭
- Activating 阶段:确保新的 sw 能顺利接管控制权,主要用来清理旧缓存或其他sw的资源
- Activated 阶段:activated 阶段完成,sw 可以处理 Functional events(见下)
- Redundant 阶段:当一个sw 被另外一个替代时(此时当前 sw 就是过期)
四、事件
- Install 事件:安装事件在 Service Worker 被安装时触发。通常用于缓存静态资源,以便在离线时使用。
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/scripts/main.js',
]);
})
);
});
- Activate 事件:事件在 Service Worker 被激活时触发。用于清理旧缓存并确保新的 Service Worker 接管控制权。
self.addEventListener('activate', (event) => {
const cacheWhitelist = ['my-cache'];
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});
- Message 事件(Activated 阶段):消息事件在 Service Worker 接收到消息时触发。可以用于与主线程进行通信。
// 主线程
navigator.serviceWorker.ready.then((registration) => {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = (event) => {
console.log("Received reply from Service Worker:", event.data);
};
registration.active.postMessage(
{ type: "MESSAGE_IDENTIFIER", payload: "Hello from main thread!" },
[messageChannel.port2]
);
});
self.addEventListener('message', (event) => {
console.log('Received message:', event.data);
if (event.data && event.data.type === 'MESSAGE_IDENTIFIER') {
// 处理消息
console.log('Payload:', event.data.payload);
}
});
- Fetch 事件:抓取事件在 Service Worker 拦截网络请求时触发。用于提供缓存响应或自定义网络请求处理。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((response) => {
return caches.open('my-cache').then((cache) => {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});
- Sync 事件:同步事件在网络连接恢复时触发。用于后台同步数据。
// 主程
navigator.serviceWorker.ready.then((registration) => {
return registration.sync.register('sync-tag');
});
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-tag') {
event.waitUntil(
// 执行同步任务,例如发送未发送的请求
fetch('/sync-endpoint', {
method: 'POST',
body: JSON.stringify({ data: 'your data' }),
headers: {
'Content-Type': 'application/json'
}
);
}
});
- Push 事件:推送事件在接收到推送通知时触发。用于显示通知。(由服务器端推送服务产生事件派发)
self.addEventListener('push', (event) => {
const data = event.data.json();
self.registration.showNotification(data.title, {
body: data.body,
icon: '/images/icon.png',
});
});
五、如何使用?
与 Web Worker 类似,来自一个域的多个页面共享一个 Service Worker。
1、第一步需要对当前的 Web 页面进行注入 Service Worker 代码,通过 navigator.serviceWorker.register
实现注册(进入 Installing 阶段)。
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('Service Worker registered: ', registration);
})
.catch((registrationError) => {
console.log('Service Worker registration failed: ', registrationError);
});
});
}
如果是 React 项目,可以在根组件首次加载时注册
export default function App() {
...
useEffect(() => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('Service Worker 注册成功: ', registration);
})
.catch((registrationError) => {
console.log('Service Worker 注册失败: ', registrationError);
});
},[])
...
}
2、编写 Service Worker 代码(上面的 sw.js 文件内容)。第一次编写主要是缓存静态文件、清除旧缓存代码。
// sw.js
// Installing 阶段时调用
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache').then((cache) => {
// 需要缓存的路径、资源文件
return cache.addAll([
'/',
'/index.html',
'/styles/main.css',
'/scripts/main.js',
]);
})
);
self.skipWaiting();
});
// Activating 阶段时调用
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
// 不是所要的缓存,删除
if (cacheName !== 'my-cache') {
return caches.delete(cacheName);
}
})
);
})
);
// 当前激活的 Service Worker 立即接管所有客户端,而不必等待页面重新加载
self.clients.claim();
});
3、拦截和处理网络请求,当客户端(Web)发送网络请求时,此时让浏览器选择走 Cache 还是发送真请求。
// sw.js
// Activated 阶段时调用
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// cache 有,直接返回
if (response) {
return response;
}
// cache 没有,发送一次新请求
return fetch(event.request).then((response) => {
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 复制到 cache,下次从这里取
const responseToCache = response.clone();
caches.open('my-cache').then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
4、注意事项
- HTTPS:Service Worker 只能在 HTTPS 或 localhost 上运行。
- 浏览器支持:确保浏览器支持 Service Worker,可以参考 Can I Use。
- 缓存管理:定期更新缓存,处理缓存失效的情况。
- PWA:2023年下半年开始,部分浏览器弹出安装应用程序提示框,不需要 Service Worker
通过以上步骤,你可以快速上手Service Worker,通过掌握这一技术,将让你的Web应用更加贴近原生应用体验,提升用户体验与应用的竞争力。
延展阅读:
如何搭建PostgreSQL高可用方案repmgr?详解安装部署与关键组件疑问
如何快速上手LoadRunner VuGen进行Web项目性能测试?
MongoDB Change Streams可以应用在哪些场景?它有哪些局限性?
MongoDB 4.0至7.0:这些主版本更新带来了哪些关键特性与性能飞跃?
咨询方案 获取更多方案详情