介绍
Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API。
Service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHR和localStorage不能在service worker中使用。
出于安全考量,Service workers只能由HTTPS承载,毕竟修改网络请求的能力暴露给中间人攻击会非常危险。在Firefox浏览器的用户隐私模式,Service Worker不可用。
Service workers之所以优于以前同类尝试(如AppCache),是因为它们无法支持当操作出错时终止操作。Service workers可以更细致地控制每一件事情。
Service workers大量使用Promise,因为通常它们会等待响应后继续,并根据响应返回一个成功或者失败的操作。Promise非常适合这种场景。
根据官方的介绍不难看出,Service worker提供了更有效的缓存请求资源控制手段。
基本使用
使用ServiceWorkerContainer.register()
注册service worker,这将作用于整个域内用户可访问的URL,或者其特定子集。
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
}
taobaofed为什么说 SW是网站的大脑?举个例子,如果在 www.example.com
的根路径下注册了一个 SW,那么这个 SW 将可以控制所有该浏览器向 www.example.com
站点发起的请求。只需要监听 fetch 事件,你就可以任意的操纵请求,可以返回从 CacheStorage 中读的数据,也可以通过 Fetch API 发起新的请求,甚至可以 new 一个 Response,返回给页面。
直接看一个完整的sw文件示例去体会用法:
var cacheStorageKey = 'cachesName';
var cacheList = [
// 注册成功后要立即缓存的资源列表
]
// 当浏览器解析完 SW 文件时触发 install 事件
self.addEventListener('install', function(e) {
// install 事件中一般会将 cacheList 中要缓存的内容通过 addAll 方法,
// 请求一遍放入 caches 中
e.waitUntil(
caches.open(cacheStorageKey).then(function(cache) {
return cache.addAll(cacheList)
})
);
});
// 激活时触发 activate 事件
self.addEventListener('activate', function(e) {
// active 事件中通常做一些过期资源释放的工作,匹配到就从 caches 中删除
var cacheDeletePromises = caches.keys().then(cacheNames => {
return Promise.all(cacheNames.map(name => {
if (name !== cacheStorageKey) {
return caches.delete(name);
} else {
return Promise.resolve();
}
}));
});
e.waitUntil(
Promise.all([cacheDeletePromises])
);
});
self.addEventListener('fetch', function(e) {
// 在此编写缓存策略
e.respondWith(
// 可以通过匹配缓存中的资源返回
caches.match(e.request)
// 也可以从远端拉取
fetch(e.request.url)
// 也可以自己造
new Response('自己造')
// 也可以通过吧 fetch 拿到的响应通过 caches.put 方法放进 caches
);
});
监听install事件,在解析完后写入需要缓存的cache
监听activate事件,触发时可以清理旧缓存和旧的service worker关联的东西。
监听fetch事件,去响应请求,通过respondWith去任意修改对于这些请求的响应。
示例也基本符合了MDN官方给出的使用建议。
Workbox 3
workbox被定义为 PWA 相关的工具集合,可以把它理解为 Google 官方的 PWA 框架,它解决的就是用底层 API 写 PWA 太过复杂的问题,让管理service worker更加简单。
直接来看taobaofed给出的案例
// 首先引入 Workbox 框架
importScripts('[https://storage.googleapis.com/workbox-cdn/releases/3.3.0/workbox-sw.js](https://storage.googleapis.com/workbox-cdn/releases/3.3.0/workbox-sw.js)');
workbox.precaching([
// 注册成功后要立即缓存的资源列表
]);
// html的缓存策略
workbox.routing.registerRoute(
new RegExp(''.*\.html'),
workbox.strategies.networkFirst()
);
workbox.routing.registerRoute(
new RegExp('.*\.(?:js|css)'),
workbox.strategies.cacheFirst()
);
workbox.routing.registerRoute(
new RegExp('https://your\.cdn\.com/'),
workbox.strategies.staleWhileRevalidate()
);
workbox.routing.registerRoute(
new RegExp('[https://your\.img\.cdn\.com/](https://your/.img/.cdn/.com/)'),
workbox.strategies.cacheFirst({
cacheName: 'example:img'
})
);
对比原先就比较清晰明了。workbox.precaching
即install时塞进caches的内容。workbox.routing.registerRoute
去匹配请求路径,匹配上了走相应的请求策略,等于activate和fetch方法都合并在了一个方法里去配置,更加独立简介明了。
缓存策略
- Stale-While-Revalidate 当请求的路由有对应的 Cache 缓存结果就直接返回,在返回 Cache 缓存结果的同时会在后台发起网络请求拿到请求结果并更新 Cache 缓存,如果本来就没有 Cache 缓存的话,直接就发起网络请求并返回结果,这对用户来说是一种非常安全的策略,能保证用户最快速的拿到请求的结果,但是也有一定的缺点,就是还是会有网络请求占用了用户的网络带宽。
- Cache First (Cache Falling Back to Network) 当匹配到请求之后直接从 Cache 缓存中取得结果,如果 Cache 缓存中没有结果,那就会发起网络请求,拿到网络请求结果并将结果更新至 Cache 缓存,并将结果返回给客户端。这种策略比较适合结果不怎么变动且对实时性要求不高的请求。
- Network First (Network Falling Back to Cache) 请求路由是被匹配的,就采用网络优先的策略,也就是优先尝试拿到网络请求的返回结果,如果拿到网络请求的结果,就将结果返回给客户端并且写入 Cache 缓存,如果网络请求失败,那最后被缓存的 Cache 缓存结果就会被返回到客户端,这种策略一般适用于返回结果不太固定或对实时性有要求的请求,为网络请求失败进行兜底。
- Network Only 直接强制使用正常的网络请求,并将结果返回给客户端,这种策略比较适合对实时性要求非常高的请求。
- Cache Only 直接使用 Cache 缓存的结果,并将结果返回给客户端,这种策略比较适合一上线就不会变的静态资源请求。
总结心得
虽然MDN上指出IE、Safari目前并不支持,参考淘宝 PC 首页的 Service Worker 上线已经有好长一段时间了,其实是可以考虑逐渐引入使用的。且因为通过官方提供的底层API去写可能太过复杂,直接使用workbox是一个不错的策略!
参考
Workbox 3:Service Worker 可以如此简单
Service_Worker_API from MDN
workbox缓存策略