在现代的网络世界里,5G 网络的普及,我们可以访问一个网站或者使用一个 App 的速度极其快,但是在没有网络的情况下你啥都看不了,只能大眼瞪小眼了。
离线应用是指通过离线缓存技术,让资源在第一次被加载后缓存在本地,下次访问它时就直接返回本地的文件,就算没有网络连接。
通过离线应用,主要有以下几个优点:
- 在没有网络的情况下也能打开网页。
- 由于部分被缓存的资源直接从本地加载,对用户来说可以加速网页加载速度,对网站运营者来说可以减少服务器压力以及传输流量费用。
离线应用的核心是离线缓存技术,要实现这种方式,我们可以使用 Service Worker 来实现这种缓存技术。
什么是 Service Worker
Service Worker 服务器和浏览器之间的之间的桥梁或者中间人。
Service Worker 运行在一个与页面 JavaScript 主线程独立的线程上,并且无权访问 DOM 结构。但是它能拦截当前网站所有的请求,对请求使用相应的逻辑进行判断,如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器。从而大大提高浏览体验。
注册 Service Worker
要使用 Service Worker,首先我们要判断浏览器是否支持 Service Worker,具体代码逻辑如下:
js
if (navigator.serviceWorker) {
window.addEventListener("DOMContentLoaded", function () {
navigator.serviceWorker.register("/worker.js");
});
}
这段代码的主要目的是在支持 Service Worker 的浏览器中,当页面加载完成后注册一个指定的 Service Worker 脚本。这个传入的 worker.js 就是 Service Worker 的运行环境。
这个脚本被安装到浏览器中后,就算用户关闭了当前网页,它仍会存在。 也就是说第一次打开该网页时 Service Workers 的逻辑不会生效,因为脚本还没有被加载和注册,但是以后再次打开该网页时脚本里的逻辑将会生效。
Service Worker 安装和激活
注册完成后,worker.js 文件会自动下载、安装,然后激活。它提供了一些 API 给我们做一些监听事件:
js
self.addEventListener("install", function (e) {
console.log("Service Worker 安装成功");
});
self.addEventListener("fetch", function (event) {
console.log("service worker is fetch");
});
当 install 完成并且成功激活之后,就能够监听 fetch 操作了,如上代码所示,输出结构如下图所示:
使用 Service Workers 实现离线缓存
在上面的内容我们已经知道了 Service Workers 在注册成功后会在其生命周期中派发出一些事件,通过监听对应的事件在特点的时间节点上做一些事情。
在 Service Workers 安装成功后会派发出 install 事件,需要在这个事件中执行缓存资源的逻辑,实现代码如下:
js
// 当前缓存版本的唯一标识符,用当前时间代替
const cacheKey = new Date().toISOString();
// 需要被缓存的文件的 URL 列表
const cacheFileList = ["/index.html", "/index.js", "/index.css"];
// 监听 install 事件
self.addEventListener("install", function (event) {
// 等待所有资源缓存完成时,才可以进行下一步
event.waitUntil(
caches.open(cacheKey).then(function (cache) {
// 要缓存的文件 URL 列表
return cache.addAll(cacheFileList);
})
);
});
在 install 阶段我们就已经指定了要被缓存的内容了,那么就可以在 fetch 阶段中听网络请求事件去拦截请求,复用缓存,代码如下:
js
self.addEventListener("fetch", function (event) {
event.respondWith(
// 去缓存中查询对应的请求
caches.match(event.request).then(function (response) {
// 如果命中本地缓存,就直接返回本地的资源
if (response) {
return response;
}
// 否则就去用 fetch 下载资源
return fetch(event.request);
})
);
});
通过上面的操作,创建和添加了一个缓存的库,如下图所示:
缓存更新
线上的代码有时需要更新和重新发布,如果这个文件被离线缓存了,那就需要 Service Workers 脚本中有对应的逻辑去更新缓存。
这可以通过更新 Service Workers 脚本文件做到,浏览器针对 Service Worker 有如下机制:
- 每次打开接入了 Service Workers 的网页时,浏览器都会去重新下载 Service Workers 脚本文件,如果发现和当前已经注册过的文件存在字节差异,就将其视为新服务工作线程。
- 新 Service Workers 线程将会启动,且将会触发其 install 事件。
- 当网站上当前打开的页面关闭时,旧 Service Workers 线程将会被终止,新 Service Workers 线程将会取得控制权。
- 新 Service Workers 线程取得控制权后,将会触发其 activate 事件。
新 Service Workers 线程中的 activate 事件就是最佳的清理旧缓存的时间点,代码如下:
js
var cacheWhitelist = [cacheKey];
self.addEventListener("activate", function (event) {
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
// 不在白名单的缓存全部清理掉
if (cacheWhitelist.indexOf(cacheName) === -1) {
// 删除缓存
return caches.delete(cacheName);
}
})
);
})
);
});
这样能确保只有那些我们需要的文件会保留在缓存中,我们不需要留下任何的垃圾,毕竟浏览器的缓存空间是有限的,手动清理掉这些不需要的缓存是不错的主意。
参考资料
总结
Service Worker 作为服务器和浏览器两者之间的桥梁,它并且可以缓存技术,通过这种方式,在断网的时候,去获取缓存中相应的数据以展示给客户显示。
当断网之后,直接给他页面返回一个俄罗斯方块让他玩足一整天。