# Service Worker ## 含义 Service Worker 首先是一个运行在后台的 Worker 线程,然后它会长期运行,充当一个服务,很适合那些不需要网页或用户互动的功能。它的最常见用途就是拦截和处理网络请求。 Service Worker 是一个后台运行的脚本,充当一个代理服务器,拦截用户发出的网络请求,比如加载脚本和图片。Service Worker 可以修改用户的请求,或者直接向用户发出回应,不用联系服务器,这使得用户可以在离线情况下使用网络应用。它还可以在本地缓存资源文件,直接从缓存加载文件,因此可以加快访问速度。 ```javascript if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/service-worker.js'); }); } ``` 上面代码确认浏览器支持 Service Worker 以后,会注册一个 Service Worker。 为了节省内存,Service worker 在不使用的时候是休眠的。它也不会保存数据,所以重新启动的时候,为了拿到数据,最好把数据放在 IndexedDb 里面。 Service Worker 是事件驱动的。 下面是拦截请求的例子。 ```javascript self.addEventListener('fetch', (event) => { event.waitUntil( if (event.request.url.includes('/product') { let productId = event.data.productId let productCount = getProductData(productId) indexedDB.open('store', 1, (db) => { let productStore = db.createObjectStore('products', { keyPath: 'id' }) productStore.put({ id: productId, count: ++productCount }) }) }) ) }) ``` Service Worker 不能直接操作 DOM。 ## 使用步骤 ### 登记 使用 service worker 的第一步,就是告诉浏览器,需要注册一个 service worker 脚本。 ```javascript navigator.serviceWorker.register('sw.js'.then(() => { console.info('注册成功') }).catch((err) => { console.error('注册失败') }) ``` 上面代码的`sw.js`就是需要浏览器注册的 service worker 脚本。注意,这个脚本必须与当前网址同域,service worker 不支持跨域脚本。另外,`sw.js`必须是从 HTTPS 协议加载的。 默认情况下,Service worker 只对根目录`/`生效,如果要改变生效范围,可以运行下面的代码。 ```javascript navigator.serviceWorker.register( '/service-worker.js', { scope: '/products/fashion' } ) ``` ### 安装 一旦登记成功,接下来都是 service worker 脚本的工作。下面的代码都是写在 service worker 脚本里面的。 登记后,就会触发`install`事件。service worker 脚本需要监听这个事件。 ```javascript self.addEventListener('install', event => { event.waitUntil(() => console.info('安装完成')) }) ``` `event.waitUntil()`方法为事件完成后指定回调函数。 ```javascript self.addEventListener('install', (event) => { let CACHE_NAME = 'xyz-cache' let urlsToCache = [ '/', '/styles/main.css', '/scripts/bundle.js' ] event.waitUntil( caches.open(CACHE_NAME) .then (cache => cache.addAll(urlsToCache)) ) }) ``` ### 激活 安装完成后,service worker 就会等待激活。 ```javascript self.addEventListener('activate', (event) => { let cacheWhitelist = ['products-v2'] event.waitUntil( caches.keys().then (cacheNames => { return Promise.all( cacheNames.map( cacheName => { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName) } }) ) }) ) }) ``` ## Service Worker 与网页的通信 ```javascript self.addEventListener('activate', (event) => { event.waitUntil( self.clients.matchAll().then ( (client) => { client.postMessage({ msg: 'Hey, from service worker! I\'m listening to your fetch requests.', source: 'service-worker' }) }) ) }) ``` 上面代码中,Service Worker 监听`activate`事件,然后向客户端发送一条信息。 客户端需要部署消息监听代码。 ```javascript this.addEventListener('message', (data) => { if (data.source == 'service-worker') { console.log(data.msg) } }) ``` ## 参考链接 - [Service Workers](https://frontendian.co/service-workers), by Ryan Miller