一篇文章让你了解 service-worker
WilburBSA
8年前
<h2>Service Worker 是什么?</h2> <p>service worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本。</p> <p>service worker不需要用户打开 web 页面,也不需要其他交互,异步地运行在一个完全独立的上下文环境,不会对主线程造成阻塞。基于service worker可以实现消息推送,静默更新以及地理围栏等服务。</p> <p>service worker提供一种渐进增强的特性,使用特性检测来渐渐增强,不会在老旧的不支持 service workers 的浏览器中产生影响。可以通过service workers解决让应用程序能够离线工作,让存储数据在离线时使用的问题。</p> <p>注意事项:</p> <p>1.service worker运行在它们自己的完全独立异步的全局上下文中,也就是说它们有自己的容器。</p> <p>2.service worker没有直接操作DOM的权限,但是可以通过postMessage方法来与Web页面通信,让页面操作DOM。</p> <p>3.service worker是一个可编程的网络代理,允许开发者控制页面上处理的网络请求。</p> <p>4.浏览器可能随时回收service worker,在不被使用的时候,它会自己终止,而当它再次被用到的时候,会被重新激活。</p> <p>5.service worker的生命周期是由事件驱动的而不是通过Client。</p> <h3>Service Worker生命周期</h3> <p>service worker拥有一个完全独立于Web页面的生命周期</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ddbf04453970b46ea2043791c4b40937.png"></p> <ol> <li> <p>注册service worker,在网页上生效</p> </li> <li> <p>安装成功,激活 或者 安装失败(下次加载会尝试重新安装)</p> </li> <li> <p>激活后,在sw的作用域下作用所有的页面,首次控制sw不会生效,下次加载页面才会生效。</p> </li> <li> <p>sw作用页面后,处理fetch(网络请求)和message(页面消息)事件 或者 被终止(节省内存)。</p> </li> </ol> <h3>Service Worker支持使用</h3> <p>浏览器支持</p> <p><a href="/misc/goto?guid=4959739575636535314" rel="nofollow,noindex">service worker support</a></p> <p><img src="https://simg.open-open.com/show/4cc8377d8bf12e00da56cbd6202e1a6f.png"></p> <p>polyfill</p> <p>使用 ServiceWorker cache polyfill 让旧版本浏览器支持 ServiceWorker cache API,</p> <p>https</p> <p>Server需要支持https</p> <p>通过service worker可以劫持连接,伪造和过滤响应,为了避免这些问题,只能在HTTPS的网页上注册service workers,防止加载service worker的时候不被坏人篡改。</p> <p>Github Pages是HTTPS的,可以通过Github做一些尝试</p> <h3>调试工具</h3> <p>在调试的时候可以用于unregister、stop、start等操作</p> <p>chrome访问 chrome://inspect/#service-workers 或 chrome://serviceworker-internals 查看service-workers</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/f82dbd62176309ac96715d1d2283442c.png"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/65d6e6fc6bb2d92be17e6dbd6bed781d.png"></p> <p>firefox通过 about:debugging#workers 查看</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8445820c73264901dbe77b02f5cb16fd.png"></p> <h2>离线存储数据</h2> <p>对URL寻址资源,使用 Cache API 。对其他数据,使用IndexedDB。</p> <h2>离线阅读</h2> <h3>demo</h3> <p>使用 https 访问本文,打开ChromeDevTools,选择Application选项卡->Service Workers</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/c420c1cc6263a2a2a7769de38693ed78.png"></p> <p>可以看到Service Workers注册</p> <p>点击下面离线保存按钮</p> <p>然后选择Cache Storage,可以看到文字内容已经缓存到Cache Storage</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/131f5bb4b041496f0d225b088c33d8b1.png"></p> <p>然后选择Service Workers 勾选 Offline,NetWork出现了:warning:️,然后试试离线访问本文:sunglasses:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/e39f48a9d1f1d00c7b07ca6f23ce1556.png"></p> <h3>原理</h3> <p>注册 service worker</p> <p>创建一个 JavaScript 文件(比如:sw.js)作为 service worker</p> <p>告诉浏览器注册这个JavaScript文件为service worker,检查service worker API是否可用,如果可用就注册service worker</p> <pre> <code class="language-javascript">if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function(err) { // registration failed :( console.log('ServiceWorker registration failed: ', err); }); }</code></pre> <p>sw.js文件被放在这个域的根目录下,和网站同源。这个service work将会收到这个域下的所有fetch事件。</p> <p>如果将service worker文件注册为/example/sw.js,那么,service worker只能收到/example/路径下的fetch事件(例如: /example/page1/, /example/page2/)。</p> <pre> <code class="language-javascript">// Service Workers if ('serviceWorker' in navigator && navigator.onLine) { navigator.serviceWorker.register('/sw.js').then(function(registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function(err) { console.log('ServiceWorker registration failed: ', err); }); var currentPath = window.location.pathname; var cacheButton = document.querySelector('.offline-btn'); var imageArray = document.querySelectorAll('img'); if(cacheButton) { cacheButton.addEventListener('click', function(event) { event.preventDefault(); // 缓存当前链接和使用的图片 var pageResources = [currentPath]; for (i = 0; i < imageArray.length; i++) { pageResources.push(imageArray[i].src); } caches.open('offline-' + currentPath).then(function(cache) { var updateCache = cache.addAll(pageResources); updateCache.then(function() { console.log('Article is now available offline.'); cacheButton.innerHTML = "☺"; }); updateCache.catch(function (error) { console.log('Article could not be saved offline.'); cacheButton.innerHTML = "☹"; }); }); }); } }</code></pre> <p>缓存站点的资源</p> <p>定义需要缓存的文件,然后在sw注册安装后回到cache Api将资源文件写入缓存。如果所有的文件都被缓存成功了,那么service worker就安装成功了。如果任何一个文件下载失败,那么安装步骤就会失败。</p> <pre> <code class="language-javascript">var cacheName = 'v1'; var assetsToCache = [ '/styles/main.css', '/script/main.js' ]; self.addEventListener('install', function(event) { event.waitUntil( caches.open(cacheName).then(function(cache) { return cache.addAll(assetsToCache); }).then(function() { return self.skipWaiting(); }) ); });</code></pre> <p>从缓存中加载</p> <p>service worker成功注册,并且用户浏览了另一个页面或者刷新了当前的页面,service worker将开始接收到fetch事件。</p> <p>拦截网络请求并使用缓存,缓存命中,返回缓存资源,否则返回一个实时从网络请求fetch的结果。</p> <pre> <code class="language-javascript">self.addEventListener('fetch', function(event) { var requestUrl = new URL(event.request.url); if (requestUrl.origin === location.origin) { if (requestUrl.pathname === '/') { event.respondWith( caches.open(cacheName).then(function(cache) { return fetch(event.request).then(function(networkResponse) { cache.put(event.request, networkResponse.clone()); return networkResponse; }).catch(function() { return cache.match(event.request); }); }) ); } } event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); });</code></pre> <p>缓存版本管理</p> <p>版本修改的时候会触发activate,将旧版本的缓存清理掉。</p> <pre> <code class="language-javascript">var OFFLINE_PREFIX = 'offline-'; var CACHE_NAME = 'main_v1.0.0'; self.addEventListener('activate', function(event) { var mainCache = [CACHE_NAME]; event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if ( mainCache.indexOf(cacheName) === -1 && cacheName.indexOf(OFFLINE_PREFIX) === -1 ) { // When it doesn't match any condition, delete it. console.info('SW: deleting ' + cacheName); return caches.delete(cacheName); } }) ); }) ); return self.clients.claim(); });</code></pre> <h2>Service Worker 库</h2> <ul> <li> <p><a href="/misc/goto?guid=4959739575727608778" rel="nofollow,noindex">sw-toolbox</a></p> </li> <li> <p><a href="/misc/goto?guid=4959739575812836864" rel="nofollow,noindex">sw-precache</a></p> </li> <li> <p><a href="/misc/goto?guid=4959739575896108062" rel="nofollow,noindex">offline-plugin</a> ,webpack离线插件</p> </li> </ul> <h2>参考</h2> <ul> <li> <p><a href="/misc/goto?guid=4959733392202952323" rel="nofollow,noindex">your-first-pwapp</a></p> </li> <li> <p><a href="/misc/goto?guid=4959733392273143433" rel="nofollow,noindex">service-workers</a></p> </li> <li> <p><a href="/misc/goto?guid=4959739576062894721" rel="nofollow,noindex">渐进式 Web App 的离线存储</a></p> </li> </ul> <p> </p> <p>来自:http://kailian.github.io/2017/03/01/service-worker</p> <p> </p>