Service Worker介绍:
Service Worker 是一种运行在浏览器后台的脚本,它为网络应用提供了事件驱动的API,使得开发者可以创建更丰富的离线体验和实时功能。Service Worker 运行在它自己的线程中,因此不会影响页面的性能。以下是 Service Worker 的一些关键特性和用途:
-
离线体验: Service Worker 允许开发者缓存文件,这样即使用户处于离线状态,应用仍然可以工作。
-
消息推送和通知: Service Worker 支持推送消息,即使应用没有打开,也可以发送通知给用户。
-
后台同步: Service Worker 可以在后台同步数据,这对于需要保持数据最新状态的应用非常有用。
-
拦截网络请求: Service Worker 可以拦截对资源的网络请求,这意味着开发者可以决定如何响应这些请求,比如从缓存中提供资源或者从网络获取。
-
更新和部署: Service Worker 支持应用的更新和部署,可以控制资源的更新策略。
-
生命周期管理: Service Worker 有明确的生命周期,包括注册、安装、激活和注销阶段。
-
多页面共享: 一个 Service Worker 可以控制多个页面,这意味着它可以为整个应用提供统一的缓存和网络请求管理。
-
安全性: Service Worker 只能通过 HTTPS 来注册,这是为了保证通信的安全。
-
性能优化: Service Worker 可以用于性能优化,比如通过预加载资源来加快页面加载速度。
-
Web 应用清单: Service Worker 通常与 Web 应用清单(Web App Manifest)一起使用,后者定义了应用的名称、图标、启动画面等信息。
Service Worker 的使用流程大致如下:
- 注册:在页面的 JavaScript 中注册 Service Worker。
- 安装:Service Worker 脚本被下载并执行安装阶段的代码。
- 激活:安装完成后,Service Worker 被激活。
- 控制:激活后,Service Worker 开始控制页面,并可以拦截网络请求。
- 更新:浏览器会在后台检查 Service Worker 脚本的更新。
- 注销:当不再需要 Service Worker 时,可以手动注销。
Service Worker生命周期:
-
注册(Register):
通过navigator.serviceWorker.register()
方法注册 Service Worker。如果注册成功,会返回一个ServiceWorkerRegistration
对象,否则会抛出错误。 -
安装(Install):
注册成功后,Service Worker 进入安装阶段。这个阶段会触发install
事件,开发者可以在这个阶段执行初始化操作,如缓存必要的资源。install
事件的回调函数必须完成执行,Service Worker 才能进入下一个阶段。 -
等待(Wait):
安装完成后,Service Worker 进入等待(waiting)状态。在这个阶段,Service Worker 还没有控制任何页面,直到所有旧版本的 Service Worker 控制的页面被关闭。 -
激活(Activate):
当所有旧版本的 Service Worker 控制的页面都关闭后,或者通过self.skipWaiting()
方法跳过等待阶段,Service Worker 进入激活(activating)状态。这个阶段会触发activate
事件,通常用于清理旧缓存和旧资源。 -
控制(Control):
激活完成后,Service Worker 开始控制页面,可以拦截和处理网络请求(fetch
事件)和接收来自页面的消息(message
事件)。 -
更新(Update):
浏览器会定期检查 Service Worker 的更新。如果有新版本的 Service Worker 可用,它将进入一个新的安装周期,而旧版本的 Service Worker 将继续控制当前页面,直到它们被关闭。 -
注销(Unregister):
当不再需要 Service Worker 时,可以通过navigator.serviceWorker.getRegistration().then(registration => registration.unregister())
方法注销 Service Worker。注销后,Service Worker 将停止工作,所有与之关联的缓存也会被清除。
生命周期示意图如下:
下面基于serviceworker写一个实际的案例来演示一下:
1.,我先基于nodejs创建一个server.js文件,搭建web服务器,以及一个api.json的文件数据,用于模拟后端网络请求,代码如下所示:
// server.js
const http = require("http");
const data = require("./api.json");
const delay = (wait = 1000) => {
let startTime = Date.now();
while (Date.now() - startTime < wait) {}
};
http
.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
if (req.url === "/list") {
// 针对/list接口,延迟3s返回数据
delay(3000);
res.write(JSON.stringify(data));
res.end();
} else {
res.end("Hello World\n");
}
})
.listen(3000, () => {
console.log("服务运行在3000端口上");
});
// api.json
[
{ "id": 1, "name": "abc" },
{ "id": 2, "name": "bcd" }
]
2.创建html文件,用于页面展示来测试serviceworker缓存效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>serverworker</title>
<link rel="shortcut icon" href="#" />
</head>
<style>
.box {
padding: 20px;
border: 1px solid #999;
}
#deleteBtn {
display: none;
}
</style>
<body>
<div class="box">
<button id="fetch-btn">获取数据</button>
<button id="fetch-cache-btn">从缓存获取数据</button>
<p>
<span>接口数据:</span>
<code></code>
<button id="deleteBtn">删除</button>
</p>
</div>
</body>
<script>
// 使用nodejs模拟后端接口,然后使用service-worker对接口进行离线缓存
window.onload = async function () {
const getData = () => {
return fetch('http://localhost:3000/list').then(res => res.json()).then(data => {
document.getElementsByTagName('code')[0].innerHTML = JSON.stringify(data);
document.getElementById('deleteBtn').style.display = 'block';
})
}
if ('serviceWorker' in navigator) {
const registration = navigator.serviceWorker.register('./service-worker.js')
}
document.getElementById('fetch-cache-btn').addEventListener('click', function () {
navigator.serviceWorker.controller.postMessage({ isCache: true });
getData();
})
document.getElementById('fetch-btn').addEventListener('click', function () {
navigator.serviceWorker.controller.postMessage({ isCache: false });
getData();
})
document.getElementById('deleteBtn').onclick = function () {
document.getElementsByTagName('code')[0].innerHTML = ''
document.getElementById('deleteBtn').style.display = 'none';
}
}
</script>
</html>
3.紧接着,创建一个serviceworker.js文件,包括一些生命周期事件等逻辑;
// install事件:只有第一次加载会执行
self.addEventListener("install", function (event) {
console.log("注册install事件");
// skipWaiting:可防止出现等待情况,这意味着 Service Worker 在安装完后立即激活
self.skipWaiting();
// waitUntil:确保 Service Worker 脚本在完成某些操作后再继续执行后续的生命周期事件
event.waitUntil(
// 将/list路径缓存到v1下
caches.open("v1").then(function (cache) {
return cache.addAll(["http://localhost:3000/list"]);
})
);
});
// activate事件:激活serviceworker
self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim());
});
// fetch事件:监听、拦截fetch请求
self.addEventListener("fetch", async (event) => {
console.log("拦截fetch", event);
if (event.request.url.endsWith("/list") && self.isCache) {
// 缓存
// event.respondWith: 保证接口请求能够被serviceworker响应
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
// 命中缓存,直接返回
return response;
} else {
let request = event.request.clone();
return fetch(request)
.then(function (res) {
if (!res || res?.status !== 200) {
// 接口返回异常
return res;
}
const responseClone = res.clone();
caches.open("v1").then(function (cache) {
console.log("重新设置缓存");
cache.put(event.request, responseClone);
});
})
.catch(function (reason) {
console.error("ServiceWorker fetch failed: ", reason);
});
}
})
);
} else {
// 不缓存
// 直接从网络获取资源
event.respondWith(fetch(event.request));
}
});
// message事件:接收来自主线程的消息,并往主线程发送消息
self.addEventListener("message", function (e) {
self.isCache = e.data.isCache;
});
4.创建一个package.json用于启动server.js以及将html文件放在端口上运行(serviceworker运行必须要在端口上)
{
"name": "serviceworker",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "nodemon server.js",
"start": "http-server -a 127.0.0.1 -p 10000 & npm run server"
},
"keywords": [],
"author": "",
"license": "ISC"
}
5.启动 `npm run start`,页面展示效果如下:分别点击【获取数据按钮】 以及 【从缓存获取数据按钮】,可以发现获取数据 - 接口数据3s返回,从缓存获取数据 - 接口数据立即返回;
这是因为在fetch事件,对接口进行了拦截和缓存,我们可以F12看到接口缓存的详细信息
不愿意手动复制代码的同学们,可以clone我的github下载测试哈
github地址:GitHub - jackywq/serviceworker-demo: 基于serviceworker一个简单的接口缓存案例
有用的话,别忘记给github点个赞,谢谢哈!