使用 ServiceWorker 提高性能_浏览器缓存

​service worker​​​ 是一种在独立的处理线程上执行后台任务的 worker,允许用户拦截网络请求并有条件地将其存储在称为 ​​CacheStorage API​​​ 的特殊缓存中,此类缓存不同于本地浏览器缓存,因为它允许在用户脱机的情况下从缓存中提供数据,还可以提高页面的性能。其工作原理建议阅读《​​ServiceWorker工作原理、生命周期和使用场景​​》

这在大多数情况下都是很有用的,例如,如果用户在一个 WIFI 连接不好的地区,Service worker 可以帮助解决这个问题,它可以先为用户提供已经看到的缓存内容,让用户有东西可看,而不是什么都没有。

注册 service worker

首先创建一个 ​​registerServiceWorker.js​​ 文件,用来维护注册 service worker ,并将其脚本引入 html 页面或者项目。

if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/service-worker.js")
.then((reg) => console.log("Service worker has been registered."))
.catch((err) =>
console.error(`Error during service worker registration:${err}`)
);
});
} else {
console.log("Service worker is not supported by browser.");
}

​navigator​​ 是一个对象,它具有关于运行脚本的应用程序的属性和方法。回到文件  ​​registerServiceWorker.js​​ 并贴入一下代码:

const cacheName = "v1";
const cachedAssets = ["script.js", "index.html"];
self.addEventListener("install", (e) => {
e.waitUntil(
caches
.open(cacheName)
.then((cache) => cache.addAll(cachedAssets))
.then(() => self.skipWaiting())
);
});

需要将存储的资源存储在一个数组中,并向 ​​window​​ 对象添加 ​​install​​ 事件监听器。一旦触发该事件,将使用 ​​cacheName​​ 和数组中的资源创建一个新的缓存文件夹。之后,需要向窗口添加 ​​activate​​ 事件,以检查是否为最新的缓存版本。

self.addEventListener("activate", (e) => {
e.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== cacheName) {
return caches.delete(cache);
}
})
);
})
);
});

最重要的部分是从缓存存储中加载数据,继续添加以下代码:

self.addEventListener("fetch", (e) => {
e.respondWith(fetch(e.request).catch(() => caches.match(e.request)));
});

上面的代码实现的功能是在窗口上添加 ​​fetch​​ 监听器,当这个事件被触发时,尝试通过使用 ​​fetch​​ API 发出网络请求来访问资源。在脱机的情况下,​​fetch​​API 不会返回任何数据,可以从缓存返回资源作为对原始请求的响应。为了控制用户脱机时发生的情况,需要一种介于本地和服务器之间的机制,允许缓存数据以便脱机查看。

在现代前端开发中,通常不需要自己的编写 ​​service worker​​ 的逻辑,对于复杂情况另说。下面是一个 VUE 项目中关于 ​​service worker​​ 的代码:

import { register } from "register-service-worker";

if (process.env.NODE_ENV === "production") {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
console.log(
"App is being served from cache by a service worker.\n" +
"For more details, visit https://goo.gl/AFskqB"
);
},
registered() {
console.log("Service worker has been registered.");
},
cached() {
console.log("Content has been cached for offline use.");
},
updatefound() {
console.log("New content is downloading.");
},
updated() {
console.log("New content is available; please refresh.");
},
offline() {
console.log(
"No internet connection found. App is running in offline mode."
);
},
error(error) {
console.error("Error during service worker registration:", error);
},
});
}

关于 ​​service worker​​ 的应用,还可以用于实现 MOCK 服务 《​​MSW:可用于浏览器和测试的Mock服务​​​​》。

Mock Service Worker 是一个 API 模拟库,它使用 Service Worker API 来拦截实际请求。 —— MSW docs

可以存储多少数据

可以存储的数据量因浏览器和设备而异,要验证可用空间,可以使用配额管理 API ​​storageQuota​​。

navigator.storageQuota.queryInfo("temporary").then(function (info) {
console.log(info.quota);
});

当达到特定阈值时,一些浏览器会提示用户是否同意继续存储更多数据,一下是主流浏览器的触发阀值:

  • Firefox 存储 50MB 数据后
  • Safari 手机最多只能使用 50MB
  • Safari 桌面没有存储限制,但在存储 5MB 后开始请求确认。

测量性能

当从 ​​service worker​​ 缓存中检索资源时,可以获得比浏览器缓存更好的性能。这意味着可以通过降低浏览器开始绘制页面所需的时间来进一步加快用户的呈现性能。数据显示,当使用 ​​service worker​​ 来增强性能时,可以看到浏览器的缓存行为可用提高 ​​50%​​。大大减少了渲染时间,这并不意味着浏览器缓存已经死了。对于WEB应用仍然需要它,可以在不支持 ​​service worker​​ 的浏览器中使用它。即使在支持 ​​service worker​​ 的浏览器中,也可以配置 ​​fetch​​ 事件代码来忽略不想拦截的请求,这时请求就会落在浏览器缓存中。

总结

当涉及到渲染时,​​service worker​​​ 可以显著提高应用程序的性能,只能在使用 https 的情况下使用,可以在不需要SSL证书的本地主机上开发它们,但在生产环境中就需要SSL证书。​​service worker​​ 可以在缓存中存储内容,离线存储页面,在互联网连接不佳的情况下显示离线页面,还可以开发像移动应用程序一样快速且用户友好的渐进式 Web 应用程序 (PWA) ,如有兴趣可以参阅《​​如何 PWA 构建现代离线应用程序​​​​》。