由于项目中有个问题涉及到了Service Worker,所以找了时间去研究了一下PWA。也趁此写一篇文章总结一下。
前言:PWA作为今年最火热的技术概念之一,对提升Web应用的安全、性能和体验有着很大的意义,非常值得我们去了解与学习。
什么是PWA
PWA,全称Progressive Web App,即渐进式WEB应用, 是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。它的优势主要体现在:
- 可在离线或网络较差的环境下正常打开页面。
- 安全(HTTPS)。
- 保持最新(及时更新)。
- 支持安装(添加到主屏幕)和消息推送。
- 向下兼容,在不支持相关技术的浏览器中仍可正常访问。
PWA本身其实是一个概念集合,它不是指某一项技术,而是通过一系列的Web技术与Web标准来优化Web App的安全、性能和体验。其中涉及到的一些技术概念包括但不限于:
- Web App Manifest
- Service Worker
- Cache API 缓存
- Push&Notification 推送与通知
- Background Sync 后台同步
本文主要讲一下Service Worker相关的东西。
Service Worker
1. 什么是Service Worker?
Service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用。
下图展示普通Web App与添加了Service Worker的Web App在网络请求上的差异:
2. 使用Service Worker
2.1. 注册Service Worker
在index.js文件里面注册Service Worker。
// index.js
// 注册service worker,service worker脚本文件为sw.js
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("./sw.js").then(function () {
console.log("Service Worker 注册成功");
});
}
值得一提的是,Service Worker里的各类操作都被设计为异步,以避免一些长时间的阻塞操作。这些异步操作都是建立在Promise的基础上的,如果你对Promise不够了解,建议去熟悉一下Promise。传送门:Promise(ES6标准入门)
2.2. 使用Service Worker
Service Worker的生命周期
当我们注册了Service Worker后,它会经历生命周期的各个阶段,同时会触发相应的事件。整个生命周期包括了:installing —> installed —> activating —> activated —> redundant。当Service Worker安装(installed)完毕后,会触发install事件;而激活(activated)后,则会触发activate事件。
下面的例子监听了install事件
// 在sw.js里面
// 监听install事件
self.addEventListener("install", function (e) {
console.log("Service Worker installed");
});
self
是Service Worker中的一个特殊的全局变量,类似于window
。self
指向当前这个Service Worker。
缓存静态资源
一般情况下,我们会列出一个需要缓存的资源列表,当Service Worker install时,会将改列表的资源缓存下来。
// sw.js
var cacheName = "v1";
var cacheFiles = ["/", "./index.html", "./index.js", "./index.css"];
// 监听install事件,安装完成后,进行文件缓存
self.addEventListener("install", e => {
console.log(e);
e.waitUntil(
caches
.open(cacheStorageKey)
.then(cache => cache.addAll(cacheList))
.then(_ => self.skipWaiting()) // 该函数可使新的sw.js马上生效。
);
});
看完这段代码,你可能会有所疑惑。caches
是个什么鬼东西?
caches
是暴露在window作用域的一个变量,我们通过caches
属性访问CacheStorage
。
CacheStorage
是一种新的本地存储,它的存储结构是这样的:
每个域有若干个存储模块,每个模块内可以存储若干个键值对。 它的键是网络请求(Request),值是请求对应的响应(Response)。 CacheStorage
的接口集中在全局变量caches
中,且仅在HTTPS协议(或localhost:*域)下可用。
我们在chrome上的devtool-application中可以看到CacheStorage
的相关信息。
介绍变量caches
常用方法
-
open(cacheName)
返回一个
Promise
,resolve为匹配cacheName
(如果不存在则创建一个新的cache)的Cache
对象。 -
keys()
返回一个
Promise
,它将使用一个包含与CacheStorage
追踪的所有命名Cache
对象对应字符串的数组来resolve。 使用该方法迭代所有Cache
对象的列表。
Cache
对象常用方法
-
match(request, options)
返回一个
Promise
对象,resolve的结果是跟Cache
对象匹配的第一个已经缓存的请求。 -
add(request)
抓取这个URL,检索并把返回的response对象添加到给定的
Cache
对象。这在功能上等同于调用fetch()
,然后使用Cache.put()
将response添加到cache
中。 -
addAll(requests)
抓取一个URL数组,检索并把返回的response对象添加到给定的
Cache
对象。 -
put(request, response)
同时抓取一个请求及其响应,并将其添加到给定的cache。
-
keys(request, options)
返回一个
Promise
对象,resolve的结果是Cache
对象key值组成的数组。
更多详细介绍和方法请查阅MDN-CacheStorage、MDN-Cache。
看到这里你可能又会问,Request???Response???
这里跟Fetch API有着密切的关系。
Request对象,用来表示资源的请求。
Response对象,用来表示一次请求的响应数据。
我们打印一下这两个东西,就非常明了了。
好了,接下来继续我们的Service Worker。
我们可以给 service worker 添加一个 fetch
的事件监听器,接着调用 event 上的 respondWith()
方法来劫持我们的 HTTP 响应,然后我们就可以进行一波操作了。
// sw.js
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(res => {
return res || fetch(e.request);
})
);
});
这里的逻辑是这样的:
- 浏览器发起请求,请求各类静态资源(html/js/css/img);
- Service Worker拦截浏览器请求,并查询当前cache;
- 若存在cache则直接返回,结束;
- 若不存在cache,则通过
fetch
方法向服务端发起请求,并返回请求结果给浏览器。
最终这里就简单实现了缓存静态资源文件的目的了。
更新静态缓存资源
我们通过修改cacheName
来达到更新缓存资源的目的。由于浏览器判断sw.js是否更新是通过字节方式,因此修改cacheName
会重新触发install并缓存资源。此外,在activate事件中,我们需要检查cacheName
是否变化,如果变化则表示有了新的缓存资源,原有缓存需要删除。
self.addEventListener("activate", e => {
e.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cache => {
if (cache !== cacheStorageKey) {
return caches.delete(cache);
}
})
);
})
);
return self.clients.claim();
});
最后我们可以在network看到请求资源的信息。
资源来自于Service Worker,时间也是在10ms左右,可以说是非常快的加载速度了,这样的体验对用户非常友好。
2.4. Service Worker其他功能
除了缓存静态资源文件以外,Service Worker还有缓存API数据,进行消息提醒,后台同步的功能。东西很多,目前还在慢慢探索当中。