关于service-worker:网易云课堂-Service-Worker-运用与实践

前言本文首先会简略介绍下前端的常见缓存形式,再引入Service Worker的概念,针对其原理和如何使用进行介绍。而后基于google推出的第三方库Workbox,在产品中进行使用实际,并对其原理进行简要分析。 作者:刘放 编辑:Ein 前端缓存简介先简略介绍一下现有的前端缓存技术计划,次要分为http缓存和浏览器缓存。 http缓存http缓存都是第二次申请时开始的,这也是个陈词滥调的话题了。无非也是那几个http头的问题: ExpiresHTTP1.0的内容,服务器应用Expires头来通知Web客户端它能够应用以后正本,直到指定的工夫为止。 Cache-ControlHTTP1.1引入了Cathe-Control,它应用max-age指定资源被缓存多久,次要是解决了Expires一个重大的缺点,就是它设置的是一个固定的工夫点,客户端工夫和服务端工夫可能有误差。所以个别会把两个头都带上,这种缓存称为强缓存,表现形式为: Last-Modified / If-Modified-SinceLast-Modified是服务器通知浏览器该资源的最初批改工夫,If-Modified-Since是申请头带上的,上次服务器给本人的该资源的最初批改工夫。而后服务器拿去比照。 若资源的最初批改工夫大于If-Modified-Since,阐明资源又被改变过,则响应整片资源内容,返回状态码200; 若资源的最初批改工夫小于或等于If-Modified-Since,阐明资源无新批改,则响应HTTP 304,告知浏览器持续应用以后版本。 Etag / If-None-Match后面提到由文件的批改工夫来判断文件是否改变,还是会带来肯定的误差,比方正文等无关紧要的批改等。所以推出了新的形式。 Etag是由服务端特定算法生成的该文件的惟一标识,而申请头把返回的Etag值通过If-None-Match再带给服务端,服务端通过比对从而决定是否响应新内容。这也是304缓存。 浏览器缓存Storage简略的缓存形式有cookie,localStorage和sessionStorage。这里就不具体介绍他们的区别了,这里说下通过localStorage来缓存动态资源的优化计划。localStorage通常有5MB的存储空间,咱们以微信文章页为例。查看申请发现,根本没有js和css的申请,因为它把全副的不须要改变的资源都放到了localStorage中:所以微信的文章页加载十分的快。 前端数据库前端数据库有WebSql和IndexDB,其中WebSql被标准废除,他们都有大概50MB的最大容量,能够了解为localStorage的加强版。 利用缓存利用缓存次要是通过manifest文件来注册被缓存的动态资源,曾经被废除,因为他的设计有些不合理的中央,他在缓存动态文件的同时,也会默认缓存html文件。这导致页面的更新只能通过manifest文件中的版本号来决定。所以,利用缓存只适宜那种长年不变动的动态网站。如此的不不便,也是被废除的重要起因。 PWA也使用了该文件,不同于manifest简略的将文件通过是否缓存进行分类,PWA用manifest构建了本人的APP骨架,并使用Servie Worker来管制缓存,这也是明天的配角。 Service WorkerService Worker实质上也是浏览器缓存资源用的,只不过他不仅仅是Cache,也是通过worker的形式来进一步优化。他基于h5的web worker,所以相对不会妨碍以后js线程的执行,sw最重要的工作原理就是: 1、后盾线程:独立于以后网页线程; 2、网络代理:在网页发动申请时代理,来缓存文件。 兼容性能够看到,基本上新版浏览器还是兼容滴。之前是只有chrome和firefox反对,当初微软和苹果也相继反对了。 成熟水平判断一个技术是否值得尝试,必定要思考下它的成熟水平,否则过一段时间又和利用缓存一样被标准摈弃就难堪了。所以这里我列举了几个应用Service Worker的页面: 淘宝网易新闻考拉所以说还是能够尝试下的。 调试办法一个网站是否启用Service Worker,能够通过开发者工具中的Application来查看: 被Service Worker缓存的文件,能够在Network中看到Size项为from Service Worker: 也能够在Application的Cache Storage中查看缓存的具体内容: 如果是具体的断点调试,须要应用对应的线程,不再是main线程了,这也是webworker的通用调试办法: 应用条件sw 是基于 HTTPS 的,因为Service Worker中波及到申请拦挡,所以必须应用HTTPS协定来保障平安。如果是本地调试的话,localhost是能够的。而咱们刚好全站强制https化,所以正好能够应用。 生命周期大略能够用如下图片来解释: 注册要应用Service Worker,首先须要注册一个sw,告诉浏览器为该页面调配一块内存,而后sw就会进入装置阶段。一个简略的注册形式: (function() { if('serviceWorker' in navigator) { navigator.serviceWorker.register('./sw.js'); }})()当然也能够思考全面点,参考网易新闻的注册形式: "serviceWorker" in navigator && window.addEventListener("load", function() { var e = location.pathname.match(/\/news\/[a-z]{1,}\//)[0] + "article-sw.js?v=08494f887a520e6455fa"; navigator.serviceWorker.register(e).then(function(n) { n.onupdatefound = function() { var e = n.installing; e.onstatechange = function() { switch (e.state) { case "installed": navigator.serviceWorker.controller ? console.log("New or updated content is available.") : console.log("Content is now available offline!"); break; case "redundant": console.error("The installing service worker became redundant.") } } } }). catch(function(e) { console.error("Error during service worker registration:", e) }) })后面提到过,因为sw会监听和代理所有的申请,所以sw的作用域就显得额定的重要了,比如说咱们只想监听咱们专题页的所有申请,就在注册时指定门路: ...

May 20, 2021 · 7 min · jiezi

Service-Workers-PWA-初体验

在前端越来越重的这个时代,页面加载速度成为了一个重要的指标。对于这个问题,业界也有一些解决方案。 浏览器缓存、协议缓存、强缓存懒加载(首屏)CDN 多域名突破下载并发限制。其实在两年前内部就对这块内容做过调研了。appCache方案?PWA方案?但是最后都没选择。之前看代码,发现是 localstroage 存代码,如果有就拿 localstroage 去用。省去了这一部分加载的时间。上个同事离职了。当时的调研结果我也忘了。只能再开始新一轮的调研,我选择的是 PWA 方案。(如果说是网速拖慢了加载速度,那么我的网页可以离线访问不就速度起飞了?)网上的资料很少。我希望我可以写一篇帮助下一个想使用 PWA 方案的人。 Service WorkersService worker是一个注册在指定源和路径下的事件驱动worker。 Service worker运行在worker上下文,因此它不能访问DOM。不同于主线程,它运行在其他线程中,所以不会造成主线程阻塞。它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用。 Service workers 本质上充当Web应用程序(服务端)与浏览器(客户端)之间的代理服务器。可以提供有效有效的离线体验,拦截网络请求。还可以推送通知。 Service Workers 需要注意的地方需要支持 HTTPS 访问你的页面。出于安全原因,Service Workers 要求必须在 HTTPS 下才能运行。(其实好多API都需要HTTPS的支持)资源路径为根目录的绝对路径。最大作用域 (scope),为资源路径。https://www.lilnong.top/static/js/sw-20190621.js的最大作用路径为/static/js/为了便于本地开发,localhost 也被浏览器认为是安全源。在已经支持 serivce workers 的浏览器的版本中,很多特性没有默认开启。如果你发现示例代码在当前版本的浏览器中怎么样都无法正常运行,你可能需要开启一下浏览器的相关配置:Firefox Nightly: 访问 about:config 并设置 dom.serviceWorkers.enabled 的值为 true; 重启浏览器;Chrome Canary: 访问 chrome://flags 并开启 experimental-web-platform-features; 重启浏览器 (注意:有些特性在Chrome中没有默认开放支持);Opera: 访问 opera://flags 并开启 ServiceWorker 的支持; 重启浏览器。service worker 声明周期下载 首次访问service worker控制的网站或页面时,service worker会立刻被下载。至少每24小时它会被下载一次。安装 首次下载会尝试安装,下载的文件是新的,尝试进行安装激活 安装成功后它会被激活如果现有service worker已启用,新版本会在后台安装,但不会被激活,这个时序称为worker in waiting直到所有已加载的页面不再使用旧的service worker才会激活新的service worker。新的service worker会被激活(成为active worker)。我们页面引入sw.js内容为a。当我们修改为b。这时候a和b都是已经安装完毕的,但是a是当前正在用的。b需要等没有页面在用a,才会进入激活状态。 ...

June 26, 2019 · 2 min · jiezi

PWA之-workbox-学习

前言:我们的应用可以分为两部分,一部分是属于主进程的(包括js(同步,异步),以及dom渲染等等),在一个时刻点,只能执行一个,要么先去渲染dom,完了再去执行js;要么执行完js,在去渲染dom,而不能同时执行js和dom渲染。 另一部分属于worker进程,它重新在后台起了一个进程,它和应用的主进程互不影响,可以同时执行。 常见的worker有,web worker, service worker, shared worker等等。 其中service worker一般作为web应用程序、浏览器和网络(如果可用)之间的代理服务。他们旨在创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API。用来构建PWA 应用使用service-worker前,我们必须要先在主进程中注册它,然后才能在service-worker进程中编写逻辑。 主进程 //index.jsif ("serviceWorker" in navigator) { // Use the window load event to keep the page load performant window.addEventListener("load", () => { navigator.serviceWorker.register("/service-worker.js").then(registration=>{ console.log("register succces...") }, err=>{ console.log("register error...",err) }); }); }service-worker进程 //service-worker.jsconsole.log('Hello from service-worker.js');service-worker的语法简介在service-worker.js中,self/this 表示 ServiceWorkerGlobalScope, 即全局的serviceworker工作环境,相当于在主进程中的window。在此文件中,js的其他api无法使用,如DOM,BOM操作等,但是大部分的js api可用,同时ES6也可以使用。 在service-worker中可以定义监听事件,然后在对应事件中进行逻辑处理。 具体api可查看 service worker MDN service-worker进程的执行流程 首先在主进程中开始注册,调用register方法,进入sw进程,在sw进程中判断如果还没有安装service worker.js,则触发install事件。开始安装一旦sw进程安装完成,会通知主进程register成功。接着在sw进程 触发到activate事件。如果已经安装过service-worker.js文件,则在注册时会发现并跳过install事件,直接进入注册成功,然后触发activate事件。然后开始在sw进程中通过fetch事件,来监听http请求,并对请求和响应进行缓存。//在service worker中监听installthis.addEventListener('install', function(event) { event.waitUntil( caches.open('v1').then(function(cache) { return cache.addAll([ '/sw-test/', '/sw-test/index.html', '/sw-test/style.css', '/sw-test/app.js', '/sw-test/image-list.js', '/sw-test/star-wars-logo.jpg', '/sw-test/gallery/', '/sw-test/gallery/bountyHunters.jpg', '/sw-test/gallery/myLittleVader.jpg', '/sw-test/gallery/snowTroopers.jpg' ]); }) );});除了 install之外,还有 activate,message,fetch,sync,push等事件。 ...

May 24, 2019 · 2 min · jiezi

service worker轻度探索 - 解决运营活动需求中的图片加载问题?

写在前面本文首发于公众号:符合预期的CoyPan做过运营活动需求的同学都知道,一般一个运营活动中会用到很多的图片资源。用户访问首页时,都会看到一个loading态,表示页面正在加载所需的所有图片资源。像下面这样:手动加载一个图片的代码也很简单:var img = new Image();img.onload = function(){ … }img.src = ‘图片地址’;之所以要提前加载所有的图片,是为了在后续的页面中使用图片时,不会因为需要加载图片而产生耗时,导致体验问题。本文所要讨论的场景就是:怎么样做到在首页加载图片后,直接在后面的业务逻辑中直接使用提前加载好的图片呢?答案就是:把图片存下来。缓存首页加载的图片我能想到的这种场景下的缓存图片方法有两种:使用浏览器的缓存。图片在第一次请求成功后,一般都会设置缓存。在页面后续的业务逻辑中,如果说想使用某图片,直接正常发起图片请求即可,浏览器会走缓存,甚至是从内存中直接返回这个图片。将加载好的Image对象直接保存在内存中。这种方法很适用canvas中画图的场景,直接把保存下来的Image对象扔到canvas的drawImage中即可。做业务需要不断的总结,思考。还能用什么方法来实现图片的缓存呢 ? 我尝试了一下Service Worker,本文将介绍一下Service Worker在这种业务场景下的应用。本文只是轻轻尝试了一下Service Worker,并未在线上项目中应用。Service WorkerService Worker是PWA的重要组成部分,其包含安装、激活、等待、销毁等四个生命周期。主要有以下的特性:一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。一旦被 install,就永远存在,除非被手动 unregister用到的时候可以直接唤醒,不用的时候自动睡眠可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)离线内容开发者可控能向客户端推送消息不能直接操作 DOM必须在 HTTPS 环境下才能工作( 或 http://localhost )异步实现,内部大都是通过 Promise 实现在本文所描述的业务场景中,主要是应用service worker的拦截代理请求和返回的功能。关于service worker的基础,谷歌开发者网站上有详细的介绍,这里就不赘述了。地址在这里:https://developers.google.com…需要注意的是,service worker一定要谨慎使用,因为它太重要了,一旦注册,站点的所有请求都会被控制。Service Worker的示例结合文章开头所描述的场景,我们先来写一些必要的业务函数。// 加载一个图片function loadImage(imgUrl) { return new Promise((resolve, reject)=>{ const img = new Image(); img.onload = function() { resolve(); }; img.src = imgUrl; });}// 加载一堆图片function loadImageList(imgList) { return Promise.all(imgList.map(function (imgUrl) { return loadImage(imgUrl); }));}下面是service worker的代码:self.addEventListener(‘install’, function (event) { console.log(‘install’);});self.addEventListener(‘fetch’, function (evt) { evt.respondWith( caches.match(evt.request).then(function(response) { if (response) { return response; } const request = evt.request.clone(); return fetch(request).then(function (response) { if (!response || response.status !== 200 || !response.headers.get(‘Content-type’).match(/image/)) { return response; } const responseClone = response.clone(); // 流数据需要克隆一份。注意事项② caches.open(’test-cache’).then(function (cache) { cache.put(evt.request, responseClone); }); return response; }); }) )});self.addEventListener(‘activate’, function () { console.log(‘activate’); clients.claim(); // 首次activate后,就控制页面。注意事项①});注册完service worker后,我们就劫持了页面的所有请求。每一次请求经过service worker时,都会判断刚请求是否已有缓存,如果有缓存,就直接返回结果。没有缓存时,才会向服务器发起请求,并且将图片请求的结果缓存起来。在业务代码中,我们注册并使用这个service worker的代码如下:// 需要加载的图片列表const imgArr = [‘http://xxx.jpg’, ‘…’];// 注册service workerfunction registerServiceWorker() { if (‘serviceWorker’ in navigator) { return navigator.serviceWorker.register(‘http://localhost:8080/service.js’); } else { // 没有service的处理逻辑省略 }}registerServiceWorker().then(registration => { // 注意事项③ let serviceWorker; if (registration.installing) { console.log(‘registration.installing’); serviceWorker = registration.installing; } else if (registration.waiting) { console.log(‘registration.waiting’); serviceWorker = registration.waiting; } else if (registration.active) { console.log(‘registration.active’); serviceWorker = registration.active; loadImageList(imgArr); } if (serviceWorker) { serviceWorker.addEventListener(‘statechange’, function (e) { if(e.target.state === ‘activated’) { // 首次注册时 console.log(‘首次注册sw时,sw激活’); loadImageList(imgArr); } }); }}).catch(e => { console.log(e);});注意事项:正常情况下,service worker刚注册时,是不会控制页面的,即无法拦截到页面的请求。需要用户刷新页面,再次访问时,service worker才会拦截页面请求。这与我们的需求场景不符合。我们的需求是:用户首次访问请求图片资源时,就需要对返回的图片进行缓存。所以,需要在service worker进入activate状态后,通过clients.claim()来获得页面的控制权。不过,这种方式并不被提倡。service worker拦截到请求后,我们需要拷贝返回的数据流,才能存入缓存。在业务代码中,我们每次都需要调用navigator.serviceWorker.register来拿到一个service worker。浏览器会判断当前service worker的状态,返回对应的对象。我们需要保证在service worker准备无误后,再发起图片的请求。由于server worker的自身逻辑需要一定的时间,所以我们发起图片请求的时间会被延后。使用service worker后的效果以我做的运营活动项目为例,使用service worker之前,网络请求是这样的:活动页首页,首次集中请求图片活动页后续页面中,使用加载好的图片:使用service-worker之后,网络请求是这样的:活动页首页,首次集中请求图片:活动页后续页面中,使用加载好的图片:可以看到,我们成功使用service worker劫持了页面的请求,并且将图片缓存到了浏览器的cache storage中。我们来看一下浏览器的缓存。这里的缓存都是http response。另外这里多说一句,可以使用下面的代码,来查看当前网站可以使用的浏览器本地存储空间if (‘storage’ in navigator && ’estimate’ in navigator.storage) { navigator.storage.estimate().then(({usage, quota}) => { console.log(Using ${usage} out of ${quota} bytes.); });}一些思考在本文提到的场景中,我们在用户首次访问页面时,先注册了service worker,并且使service worker立即控制页面,然后再开始请求图片。这种做法延后了图片请求的发起时间,并且从上面的图中可以看到,通过service worker加载图片的耗时比正常直接请求图片耗时略长。这些因素导致首屏时间被延后了。另外,作为运营活动页,同一个用户也不会在几天内多次访问,因此service worker的【绕过网络,立即响应请求】的特性并不能很好地发挥出来。因此,在本文描述的场景中,使用service worker来做缓存并不是最佳实践。关于service worker做缓存的最佳实践以及使用场景,可以查看这篇文章:https://developers.google.com…service worker最适合的场景还是资源离线化,用户二次进入页面时可以达到资源秒加载,不会受网络状况的影响。写在后面本文从业务的角度出发,轻度探索了service worker在文章开头给出的业务场景中的应用。后续会考虑在合适的业务场景中进行应用。 ...

March 4, 2019 · 2 min · jiezi

Service Worker

当下PWA比较火,而Service Worker是实现PWA的一项关键技术,今天我们一起了解下关于Service Worker的一些基础知识和适用场景。什么是Server Worker我们先来看一下官方文档中对于Server Worker的核心定义:Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。这是一条很准确的定义,但对于不了解Service Worker的同学来说可能并不形象,下面我们更形象的理解一下这个概念。我们以去银行取钱为例子,其过程大概如下图:从上面的图中我们可以看到,银行的钱存在金库中,客户取钱时并不是直接去金库里拿,而是需要通过银行的工作人员,再告知银行工作人员需要多少钱,并出示相应凭证后,由银行工作人员从金库中拿出钱给客户,并从账户中减去相应金额。这么做的原因很容易理解,因为金库是公用的,所有客户的钱都放在里面,我们无法保证每个客户都能只拿走属于自己的钱,并按照实际情况更新金库记录。我们的应用在请求服务器资源时,其过程也是类似的:从上面的图可以看到,请求资源的过程中,HTTP协议充当了取钱过程中的银行工作人员,客户端应用需要的资源在服务器上,但应用却无法直接去服务器获取资源,而是通过HTTP协议进行,请求中指定的各种Header信息,就是取钱时的凭证。而Service Worker可以理解成,在客户端创建了一个属于自己的金库,先看图:当我们需要取钱或者获取资源时,可以先从本地的金库中拿,本地金库没有,再通过原来的流程获取。这时我们再回头看文章开始的定义,应该就能够理解了。Service Worker与Cache的关系正常情况下,客户端获取一个资源的过程有如下三步:而关于请求资源的优化,一般也集中在这三步完成:不发出请求就能够获得资源;提高服务器查找资源的速度;减小返回内容的体积;看完上面的部分我们可以发现,当使用Service Worker中已有的资源时,客户端应用获取资源并没有进入HTTP请求的流程,也就是说,通过Service Worker,客户端应用可以在不发出请求的情况下获得资源,这很容易就让我们想到缓存,那么Service Worker和我们平时经常提到的强缓存和协商缓存等是什么关系呢?整理了一个图,可以先看下:从整体上来说,应用获取一个资源的缓存类型分为上图中的四种,分别是Service Worker、Memory Cache、Disk Cache和No Cache。资源查找顺序为从左向右,找到资源则返回,未找到则继续寻找,直至最终获取资源。上面的图中可以清楚的看出Service Worker在缓存类型中的位置,也能看到跟平时经常提到的强缓存和协商缓存的关系。Service Worker使用逻辑在了解了Service Worker的概念后,我们看下Servise Worker的基本使用逻辑,使用它的基础过程是首先注册一个Woker,这时浏览器会在后台启动一个新的线程,在这个线程启动后,会按照Service Worker中的代码逻辑将一些资源缓存下来,缓存完毕后,启动对页面请求的监听,当监听到页面请求资源时可以做出相对应的响应,比如如果资源在Service Worker中缓存过了,就可以直接返回资源。注册Service Worker对象保存在window.navigator内,首先调用register方法进行注册,导入一个js文件,文件中是我们的Service Worker逻辑,代码如下:navigator.serviceWorker.register(’/sw.js’).then(function(reg){ console.log(“success”, reg);}).catch(function(err) { console.log(“error”, err);});需要注意的是Service Worker是有作用域的,它的作用域为文件的当前路径,Service Worker文件只能管理自己作用下的资源,比如abcde.com/home/sw.js 的作用域为abcde.com/home/。激活注册后的Service Worker就能在调试工具中看到了,下面是一个chrome调试面板的截图:画红框的内容是一些比较关键的信息,比如其中表明了Service Worker的文件名和路径,以及当前Service Worker的状态,Service Worker的状态分为几种,STOPPED(已停止)、STARTING(正在启动)、RUNNING(正在运行)和STOPPING(正在停止),比如上面的截图就处于RUNNING状态。只有处于running状态的Service Worker才能生效,这就需要对注册后的Service Worker进行加载和激活,注册完毕后,Service Worker会自动开始下载,下载后会触发install事件,我们可以监听这个事件并进行下载资源的操作,代码如下:const CACHE_NAME = “demo-a”;this.addEventListener(“install”, function(event) { console.log(“install service worker success”); caches.open(CACHE_NAME); let cacheResources = [“https://abcde.com/demo.js”]; event.waitUntil( caches.open(CACHE_NAME).then(cache => { cache.addAll(cacheResources); }) );});经过上面的代码,demo.js文件就被我们缓存下来了,下载完后Service Worker就会执行激活:this.addEventListener(“active”, function(event) { console.log(“service worker active success”);});此时我们通过开发者工具就能看到一个激活的Service Worker了,整体梳理一下大概是下面的过程:需要注意的是,图中灰色的部分是一个独立的特殊线程,并不是浏览器渲染页面执行js的线程,因此使用Service Worker的过程中无需担心会影响页面的渲染。更新我们注册了Service Worker后,还面临着更新的问题,当我们的业务迭代时必然要更新Service Worker,在我们理解了它的整个注册过程后,理解更新就很简单了,直接上图:当应用加载时,会下载Service Worker文件,这是在浏览器中就会有两个文件,一个是当前正在使用的Service Worker,一个是新下载的Service Worker,当新下载的文件下载完毕后,浏览器会对两个文件进行Diff操作,如果发现文件没有更新,则会丢弃掉新下载的Service Worker文件,如果发现有变化,则会加载新的Service Worker,但新加载的Service Worker会处于wating状态,并不会实际发挥作用,只有当整个浏览器中对正在运行的Service Worker没有依赖时,才会将运行中的Service Worker抛弃,将新的Servier Worker置为激活状态。常见使用场景用于浏览器缓存,提高加载速度;实现离线应用,最近PWA如此火爆;实现消息的主动推送,为web应用增加一种给力的交互方式;兼容性我们了解一些Service Worker的基础知识,以及一些比较常见的使用场景,那么目前Service Worker的兼容性如何呢,看下图目前主流的现代浏览器支持度还是不错的,但是到目前为止全系列的IE浏览器均不支持Service Worker,而且有一点感受很明显,在查资料的过程中,看了网上不少的博客,不同的博客上也有当时写博客时的Service Worker支持度,可以明显感觉到Service Worker的支持程度在快速提升,随着支持度的提升,相信会有越来越多的开发者在项目中使用这项技术。借助Service Worker,真正让PWA应用变得流行,也许就在不久的将来。 ...

March 1, 2019 · 1 min · jiezi

Service Worker

Service Worker随着前端快速发展,应用的性能已经变得至关重要,关于这一点大佬做了很多统计。你可以去看看。如何降低一个页面的网络请求成本从而缩短页面加载资源的时间并降低用户可感知的延时是非常重要的一部分。对于提升应用的加载速度常用的手段有Http Cache、异步加载、304缓存、文件压缩、CDN、CSS Sprite、开启GZIP等等。这些手段无非是在做一件事情,就是让资源更快速的下载到浏览器端。但是除了这些方法,其实还有更加强大的Service Worker线程。Service Worker与PWA的现状说起service worker就不得不提起PWA了,service worker做为PWA的核心技术之一,多年来一直被Google大力推广,这里简单介绍一下。通俗来说,PWA就是渐进式web应用(Progressive Web App)。早在16年初,Google便提出PWA,希望提供更强大的web体验,引导开发者回归开放互联网。它弥补了web对比Native App急缺的几个能力,比如离线使用、后台加载、添加到主屏和消息推送等,同时它还具备了小程序标榜的“无需安装、用完即走”的特性。虽然PWA技术已经被W3C列为标准,但是其落地情况一直以来是很让人失望的,始终受到苹果的阻碍,最重要的原因在于PWA绕过Apple Store审核,直接推给用户。如果普及,这将威胁到苹果的平台权威,也就意味着苹果与开发者的三七分成生意将会落空。所以一直以来safrai不支持mainfest以及service worker这两项关键技术,即使在18年开始支持了,但是对PWA的支持力度也远远低于安卓,具体体现在service worker缓存无法永久保存,以及service worker的API支持不够完善,一个最明显的不同在于安卓版本的PWA会保留你的登录状态,并且会系统级推送消息。而在苹果上,这两点都做不到。也就是说,iPhone上的微博PWA,每次打开都要重新登录,而且不会收到任何推送信息。另外由于某些不可描述的原因,在国内无法使用Service Worker的推送功能,虽然国内已经有两家公司做了service worker的浏览器推送,但是成熟度还有待调研。由于目前各版本手机浏览器对service worker的支持度都不太相同,同一个接口也存在差异化还有待统一,之于我们来说,也只能用Service Worker做一做PC浏览器的缓存了。Service Worker的由来Service Worker(以下简称sw)是基于WEB Worker而来的。众所周知,javaScript 是单线程的,随着web业务的复杂化,开发者逐渐在js中做了许多耗费资源的运算过程,这使得单线程的弊端更加凹显。web worker正是基于此被创造出来,它是脱离在主线程之外的,我们可以将复杂耗费时间的事情交给web worker来做。但是web worker作为一个独立的线程,他的功能应当不仅于此。sw便是在web worker的基础上增加了离线缓存的能力。当然在 Service Worker 之前也有在 HTML5 上做离线缓存的 API 叫 AppCache, 但是 AppCache 存在很多缺点,你可以亲自看看。sw是由事件驱动的,具有生命周期,可以拦截处理页面的所有网络请求(fetch),可以访问cache和indexDB,支持推送,并且可以让开发者自己控制管理缓存的内容以及版本,为离线弱网环境下的 web 的运行提供了可能,让 web 在体验上更加贴近 native。换句话说他可以把你应用里的所有静态动态资源根据不同策略缓存起来,在你下次打开时不再需要去服务器请求,这样一来就减少了网络耗时,使得web应用可以秒开,并且在离线环境下也变得可用。做到这一切你只需要增加一个sw文件,不会对原有的代码产生任何侵入,是不是很perfect?Service Worker基本特征无法操作DOM只能使用HTTPS以及localhost可以拦截全站请求从而控制你的应用与主线程独立不会被阻塞(不要再应用加载时注册sw)完全异步,无法使用XHR和localStorage一旦被 install,就永远存在,除非被 uninstall或者dev模式手动删除独立上下文响应推送后台同步。。。service worker是事件驱动的worker,生命周期与页面无关。 关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动。Dedicated Worker以及Shared Worker与Service Worker三者非常重要的区别在于不同的生命周期。对于Service Worker来说文档无关的生命周期,是它能提供可靠Web服务的一个重要基础。Service Worker生命周期register 这个是由 client 端发起,注册一个 serviceWorker,这需要一个专门处理sw逻辑的文件Parsed 注册完成,解析成功,尚未安装installing 注册中,此时 sw 中会触发 install 事件, 需知 sw 中都是事件触发的方式进行的逻辑调用,如果事件里有 event.waitUntil() 则会等待传入的 Promise 完成才会成功installed(waiting) 注册完成,但是页面被旧的 Service Worker 脚本控制, 所以当前的脚本尚未激活处于等待中,可以通过 self.skipWaiting() 跳过等待。activating 安装后要等待激活,也就是 activated 事件,只要 register 成功后就会触发 install ,但不会立即触发 activated,如果事件里有 event.waitUntil() 则会等待这个 Promise 完成才会成功,这时可以调用 Clients.claim() 接管所有页面。activated 在 activated 之后就可以开始对 client 的请求进行拦截处理,sw 发起请求用的是 fetch api,XHR无法使用fetch 激活以后开始对网页中发起的请求进行拦截处理terminate 这一步是浏览器自身的判断处理,当 sw 长时间不用之后,处于闲置状态,浏览器会把该 sw 暂停,直到再次使用update 浏览器会自动检测 sw 文件的更新,当有更新时会下载并 install,但页面中还是老的 sw 在控制,只有当用户新开窗口后新的 sw 才能激活控制页面redundant 安装失败, 或者激活失败, 或者被新的 Service Worker 替代掉Service Worker 脚本最常用的功能是截获请求和缓存资源文件, 这些行为可以绑定在下面这些事件上:install 事件中, 抓取资源进行缓存activate 事件中, 遍历缓存, 清除过期的资源fetch 事件中, 拦截请求, 查询缓存或者网络, 返回请求的资源Service Worker实践在这之前你可以先看看Google的demo我们先从sw的注册开始,官方给的demo里的注册是这样的:if (‘serviceWorker’ in navigator) { navigator.serviceWorker.register(‘service-worker.js’);}但是这样做会有一些问题,页面在首次打开的时候就进行缓存sw的资源,因为sw内预缓存资源是需要下载的,sw线程一旦在首次打开时下载资源,将会占用主线程的带宽,以及加剧对cpu和内存的使用,而且Service worker 启动之前,它必须先向浏览器 UI 线程申请分派一个线程,再回到 IO 线程继续执行 service worker 线程的启动流程,并且在随后多次在ui线程和io线程之间切换,所以在启动过程中会存在一定的性能开销,在手机端尤其严重。况且首次打开各种资源都非常宝贵,完全没有必要争第一次打开页面就要缓存资源。正确的做法是,页面加载完以后sw的事。正确的姿势:if (‘serviceWorker’ in navigator) { window.addEventListener(’load’, function() { navigator.serviceWorker.register(’/sw.js’); });}但是仅仅是这样就够了吗?只有注册,那么发生问题的时候怎么注销sw呢?注销以后缓存如何处理?这些是要提前考虑好的另外使用 sw 进行注册时,还有一个很重要的特性,即,sw的作用域不同,监听的 fetch 请求也是不一样的。假设你的sw文件放在根目录下位于/sw/sw.js路径的话,那么你的sw就只能监听/sw/*下面的请求,如果想要监听所有请求有两个办法,一个是将sw.js放在根目录下,或者是在注册是时候设置scope。一个考虑了出错降级的简易注册demo: window.addEventListener(’load’, function() { const sw = window.navigator.serviceWorker const killSW = window.killSW || false if (!sw) { return } if (!!killSW) { sw.getRegistration(’/serviceWorker’).then(registration => { // 手动注销 registration.unregister(); // 清除缓存 window.caches && caches.keys && caches.keys().then(function(keys) { keys.forEach(function(key) { caches.delete(key); }); }); }) } else { // 表示该 sw 监听的是根域名下的请求 sw.register(’/serviceWorker.js’,{scope: ‘/’}).then(registration => { // 注册成功后会进入回调 console.log(‘Registered events at scope: ‘, registration.scope); }).catch(err => { console.error(err) }) } });下面部分是sw.js文件中要做的事情,在上面注册的步骤成功以后我们首先要在sw.js文件中监听注册成功以后抛出的install事件。self.addEventListener(‘install’, function(e) { // …})通常来说,当我们监听到这个事件的时候要做的事情就是缓存所有静态文件self.addEventListener(‘install’, function(event) { event.waitUntil( caches.open(‘cache-v1’).then(function(cache) { return cache.addAll([ ‘/’, “index.html”, “main.css”, ]); }) );})这里首先执行了一个event.waitUntil函数,该函数是service worker标准提供的函数,接收一个promise参数,并且监听函数内所有的promise,只要有一个promise的结果是reject,那么这次安装就会失败。比如说cache.addAll 时,有一个资源下载不回来,即视为整个安装失败,那么后面的操作都不会执行,只能等待sw下一次重新注册。另外waitUntil还有一个重要的特性,那就是延长事件生命周期的时间,由于浏览器会随时睡眠 sw,所以为了防止执行中断就需要使用 event.waitUntil 进行捕获,当所有加载都成功时,那么 sw 就可以下一步。另外这里的缓存文件的列表通常来说我们应当使用webpack的插件或者其他工具在构建的时候自动生成。缓存的版本号也应当独立出来修改,这里我们将每一次的构建视作一个新的版本。安装成功后就会等待进入activate阶段,这里要注意的是,并不是install一旦成功就会立即抛出activate事件,如果当前页面已经存在service worker进程,那么就需要等待页面下一次被打开时新的sw才会被激活,或者使用 self.skipWaiting() 跳过等待。const cacheStorageKey = ’testCache1’;self.addEventListener(‘activate’, event => { event.waitUntil( caches.keys().then(cacheNames => { return cacheNames.filter(cacheName => cacheStorageKey !== cacheName); }).then(cachesToDelete => { return Promise.all(cachesToDelete.map(cacheToDelete => { return caches.delete(cacheToDelete); })); }).then(() => { // 立即接管所有页面 self.clients.claim() }) );});在activate中通常我们要检查并删除旧缓存,如果事件里有 event.waitUntil() 则会等待这个 Promise 完成才会成功。这时可以调用 Clients.claim() 接管所有页面,注意这会导致新版的sw接管旧版本页面。当激活完毕后就可以在fetch事件中对站点作用范围下的所有请求进行拦截处理了,你可以在这个阶段灵活的使用indexDB以及caches等api制定你的缓存规则。// 发起请求时去根据uri去匹配缓存,无法命中缓存则发起请求,并且缓存请求self.addEventListener(‘fetch’, function(event) { event.respondWith( caches.match(event.request).then(function(resp) { return resp || fetch(event.request).then(function(response) { return caches.open(‘v1’).then(function(cache) { cache.put(event.request, response.clone()); return response; }); }); }) );});event.respondWith: 接收的是一个 promise 参数,把其结果返回到受控制的 client 中,内容可以是任何自定义的响应生成代码。另外这里有一些问题:默认发起的fetch好像不会携带cookie,需要设置{ credential: ‘include’ }对于跨域的资源,需要设置 { mode: ‘cors’ } ,否则 response 中拿不到对应的数据对于缓存请求时,Request & Response 中的 body 只能被读取一次,因为请求和响应流只能被读取一次,其中包含 bodyUsed 属性,当使用过后,这个属性值就会变为 true, 不能再次读取,解决方法是,把 Request & Response clone 下来: request.clone() || response.clone()当然这只是一个demo,实际情况是不可能像这样缓存所有请求的。如果你使用工具来实现sw的话,比如sw-toolbox,通常有如下几种缓存策略:networkFirst:首先尝试通过网络来处理请求,如果成功就将响应存储在缓存中,否则返回缓存中的资源来回应请求。它适用于以下类型的API请求,即你总是希望返回的数据是最新的,但是如果无法获取最新数据,则返回一个可用的旧数据。cacheFirst:如果缓存中存在与网络请求相匹配的资源,则返回相应资源,否则尝试从网络获取资源。 同时,如果网络请求成功则更新缓存。此选项适用于那些不常发生变化的资源,或者有其它更新机制的资源。fastest:从缓存和网络并行请求资源,并以首先返回的数据作为响应,通常这意味着缓存版本则优先响应。一方面,这个策略总会产生网络请求,即使资源已经被缓存了。另一方面,当网络请求完成时,现有缓存将被更新,从而使得下次读取的缓存将是最新的。cacheOnly:从缓存中解析请求,如果没有对应缓存则请求失败。此选项适用于需要保证不会发出网络请求的情况,例如在移动设备上节省电量。networkOnly:尝试从网络获取网址来处理请求。如果获取资源失败,则请求失败,这基本上与不使用service worker的效果相同。或者根据不同的请求类型或者文件类型给予不同的策略亦或者更加复杂的策略:self.addEventListener(‘fetch’, function (event) { var request = event.request; // 非 GET 请求 if (request.method !== ‘GET’) { event.respondWith( … ); return; } // HTML 页面请求 if (request.headers.get(‘Accept’).indexOf(’text/html’) !== -1) { event.respondWith( … ); return; } // get 接口请求 if (request.headers.get(‘Accept’).indexOf(‘application/json’) !== -1) { event.respondWith( … ); return; } // GET 请求 且 非页面请求时 且 非 get 接口请求(一般请求静态资源) event.respondWith( … );}Service Worker的更新用户首次访问sw控制的网站或页面时,sw会立刻被下载。之后至少每24小时它会被下载一次。它可能被更频繁地下载,不过每24小时一定会被下载一次,以避免不良脚本长时间生效,这个是浏览器自己的行为。浏览器会将每一次下载回来的sw与现有的sw进行逐字节的对比,一旦发现不同就会进行安装。但是此时已经处于激活状态的旧的 sw还在运行,新的 sw 完成安装后会进入 waiting 状态。直到所有已打开的页面都关闭,旧的sw自动停止,新的sw才会在接下来重新打开的页面里生效。在 SW 中的更新可以分为两种,基本静态资源的更新和SW.js 文件自身的更新。但是不管是哪种更新,你都必须要对sw文件进行改动,也就是说要重新安装一个新的sw。首先假设一种情况,站点现有的sw缓存使用v1来进行命名,即在install的时候,我们使用caches.open(‘v1’)来进行预缓存,这时候旧的资源会全部存在caches里的v1下。self.addEventListener(‘install’, function(e) { e.waitUntil( caches.open(‘v1’).then(function(cache) { return cache.addAll([ “index.html” ]) }) )})现在站点更新了,我们可以简单的把chache里的v1改名为v2,这个时候由于我们修改了sw文件,浏览器会自发的更新sw.js文件并触发install事件去下载最新的文件(更新缓存可以发生在任何地方),这时新的站点会存在于v2缓存下,待到新的sw被激活之后,就会启用v2缓存。这是一种很简单并且安全的方式,相当于旧版本的自然淘汰,但毕竟关闭所有页面是用户的选择而不是程序员能控制的。另外我们还需注意一点:由于浏览器的内部实现原理,当页面切换或者自身刷新时,浏览器是等到新的页面完成渲染之后再销毁旧的页面。这表示新旧两个页面中间有共同存在的交叉时间,因此简单的切换页面或者刷新是不能使得sw进行更新的,老的sw依然接管页面,新的sw依然在等待。也就是说,即使用户知道你的站点更新了,用户自行在浏览器端做f5操作,这时,由于旧的sw还未死亡,所以用户看到的还是旧版本的页面。那么我们如何能让新的sw尽快接管页面呢?那就是在sw内部使用 self.skipWaiting() 方法。self.addEventListener(‘install’, function(e) { e.waitUntil( caches.open(cacheStorageKey).then(function(cache) { return cache.addAll(cacheList) }).then(function() { // 注册成功跳过等待,酌情处理 return self.skipWaiting() }) )})但是很明显,同一个页面,前半部分的请求是由旧的sw控制,而后半部分是由新的sw控制。这两者的不一致性很容易导致问题,除非你能保证同一个页面在两个版本的sw相继处理的情况下依然能够正常工作,才能够这样做。也就是说,我们最好能够保证页面从头到尾都是由一个sw来处理的,其实也很简单:navigator.serviceWorker.addEventListener(‘controllerchange’, () => { window.location.reload();})我们可以在注册sw的地方监听 controllerchange 事件来得知控制当前页面的sw是否发生了改变,然后刷新站点,让自己从头到尾都被新的sw控制,就能避免sw新旧交替的问题了。但是sw的变更就发生在加载页面后的几秒内,用户刚打开站点就遇上了莫名的刷新,如果你不想被用户拍砖的话我们再来考虑考虑更好的方式。毫无征兆的刷新页面的确不可接受,让我们来看看百度的lavas框架是怎么做的当检测到有新的sw被安装之后弹出一个提示栏来告诉用户站点已更新,并且让用户点击更新按钮,不过lavas这个通知栏非常简单(丑),实际应用的话我们可以在上面丰富内容,比如增加更新日志之类的东西,另外这个按钮也不够突出,我曾多次以为我按f5起到的作用和他是相同的,直到我理解了它的原理才发现只能通过点击这个按钮来完成新旧sw的更换。新的sw安装完成时会触发onupdatefound的方法,通过监听这个方法来弹出一个提示栏让用户去点击按钮。navigator.serviceWorker.register(’/service-worker.js’).then(function (reg) { // Registration.waiting 会返回已安装的sw的状态,初始值为null // 这里是为了解决当用户没有点击按钮时却主动刷新了页面,但是onupdatefound事件却不会再次发生 // 具体可以参考 https://github.com/lavas-project/lavas/issues/212 if (reg.waiting) { // 通知提示栏显示 return; } // 每当Registration.Installing属性获取新的sw时都会调用该方法 reg.onupdatefound = function () { const installingWorker = reg.installing; // installingWorker.onstatechange = function () { switch (installingWorker.state) { case ‘installed’: // 应为在sw第一次安装的时候也会调用onupdatefound,所以要检查是否已经被sw控制 if (navigator.serviceWorker.controller) { // 通知提示栏显示 } break; } }; }; }).catch(function(e) { console.error(‘Error during service worker registration:’, e); });然后就是处理通知栏点击事件之后的事情,这里只写和sw交互的部分,向等待中的sw发送消息。try { navigator.serviceWorker.getRegistration().then(reg => { reg.waiting.postMessage(‘skipWaiting’); });} catch (e) { window.location.reload();}当sw接收到消息以后,执行跳过等待操作。// service-worker.js// SW 不再在 install 阶段执行 skipWaiting 了self.addEventListener(‘message’, event => { if (event.data === ‘skipWaiting’) { self.skipWaiting(); }})接下来就是通过navigator.serviceWorker监听controllerchange事件来执行刷新操作。好了,这样一来问题就解决了,但是这种方式只能通过去点击更新按钮而无法通过用户刷新浏览器来更新。完整demoService Worker库谷歌在早期有两个pwa的轮子:sw-precachesw-toolbox都有对应的webpack插件,但是请注意,这两个从2016年开始已经不在维护了,因为有了更好的GoogleChrome/workbox,google官方也推荐大家使用workbox,百度的lavas现在也是在使用该轮子。另外还有同时支持AppCache的NekR/offline-plugin。本文作者:闫冬文章来源:Worktile技术博客文章转载请注明出处。 ...

February 22, 2019 · 3 min · jiezi

Service Worker 图片加载失败处理

Service Worker 图片加载失败处理参考文档git clone https://gitee.com/wjj0720/Service-Worker.git运行 npm i npm start访问 http://127.0.0.1:3000/pages/index.html 打开控制台 刷新(由于demo做的是后加载 需刷新后 看效果)ctrl + c 结束node服务 再次刷新页面(从缓存里面读取 依然显示页面)简介背景有一个困扰 web 用户多年的难题——丢失网络连接。之前的尝试 — AppCache — 看起来是个不错的方法,但是,它假定你使用时会遵循诸多规则,如果你不严格遵循这些规则,它会把你的APP搞得一团糟。Service worker 最终要去解决这些问题。虽然 Service Worker 的语法比 AppCache 更加复杂,但是你可以使用 JavaScript 更加精细地控制 AppCache 的静默行为。有了它,你可以解决目前离线应用的问题,同时也可以做更多的事。 Service Worker 可以使你的应用先访问本地缓存资源,所以在离线状态时,在没有通过网络接收到更多的数据前,仍可以提供基本的功能(一般称之为 Offline First)。这是原生APP 本来就支持的功能,这也是相比于 web app,原生 app 更受青睐的主要原因。什么是Service Worker ?Service Worker是浏览器在后台启动的一条服务Worker线程功能和特性: 1.一个独立的 worker 线程,且只有一个。 2.一旦被 install,就永远存在,除非被 uninstall 3.需要的时候可以直接唤醒,不需要的时候自动睡眠(此处有坑) 4.可代理请求和返回,缓存文件,缓存的文件可以被网页进程取到 5.能向客户端推送消息 6.不能直接操作 DOM 7.出于安全的考虑,必须在 HTTPS/localhost 环境下才能工作 8.异步实现,内部大都是通过 Promise 实现 9.基于web worker使用1. 注册 ```js // 兼容判断 if (“serviceWorker” in navigator) { // 一般考虑加载问题 windoe.onload后加载 window.addEventListener(“load”, function() { // scope 参数是选填的,可以被用来指定你想让 service worker 控制的内容的子目录 navigator.serviceWorker.register("/sw.js", {scope: ‘/’}) .then(function(registration) { // 注册成功 console.log( “ServiceWorker registration successful with scope: “, registration.scope ) }) .catch(function(err) { // 注册失败 console.log(“ServiceWorker registration failed: “, err) }); })}2. 使用 const precacheVersion = 2 const precacheName = “precache-v” + precacheVersion var precacheFiles = [ “/pages/index.html”, “/images/dmx.jpg”, “/images/broken.png” ]/新SW.js 浏览器会自动检查差异性发生变更 install 事件被触发 此时,旧的 SW 还在工作,新的 SW 进入 waiting 状态。注意,此时并不存在替换接管,当你现在已经打开的页面关闭时,那么旧的 SW 则会被 kill 掉。新的 SW 就开始接管页面的缓存资源。 一旦新的 SW 接管,则会触发 activate 事件。/self.addEventListener(“install”, e => {console.log("[ServiceWorker] Installed”)// skipWaiting() 方法跳过 waiting 状态,然后会直接进入 activate 阶段self.skipWaiting()e.waitUntil( caches.open(precacheName).then(cache => { // 如果其中有一个 加载失败 那就代表着–这次启动 GG return cache.addAll(precacheFiles) // cache.put(request, response).then(function() { // // 成功缓存 // }); })) })self.addEventListener(“activate”, e => {console.log("[ServiceWorker] Activated”)e.waitUntil( caches.keys().then(cacheNames => { return Promise.all( cacheNames.map(thisCacheName => { if ( thisCacheName.includes(“precache”) && thisCacheName !== precacheName ) { return caches.delete(thisCacheName) } }) ) }))// 更新客户端// self.clients.claim()})// 监听页面的请求 (不仅仅是js请求) self.addEventListener(“fetch”, e => {e.respondWith( caches.match(e.request).then(response => { // 有缓存走缓存 if (response) { return response } return fetch(e.request) .then(fetchResponse => { // console.log(’s–>’, fetchResponse); if (fetchResponse.ok) return fetchResponse; // 加载失败的情况下 入股是图片 则返回默认图片 if (isImage(e.request)) { return returnBrokenImg() } }).catch(err => { if ( isImage(e.request) ) { return returnBrokenImg() } }) }))})function isImage(fetchRequest) {return fetchRequest.method === “GET” && fetchRequest.destination === “image”;}function returnBrokenImg () {return caches.match(’/images/broken.png’).then(response => response)}// 监听页面发来的消息 self.addEventListener(‘message’, function (message, e) {console.log(‘service接受到的数据—>’, message, e);sendMessageToPage(‘嘟嘟嘟’)});// 向页面发送消息 function sendMessageToPage (msg) {self.clients.matchAll().then(function (clients) { if (clients && clients.length) { clients.forEach(function (client) { client.postMessage(msg); }) }})}3. 客户端更新> 除了由浏览器触发更新之外,如果24小时没有更新,会强制更新。这意味着最坏情况下Service Worker会每天更新一次//localStorage 存下 版本 运行时候对比var version = ‘precache-v3’navigator.serviceWorker.register(’/sw.js’).then(function (reg) { if (localStorage.getItem(‘sw_version’) !== version) { reg.update().then(function () { localStorage.setItem(‘sw_version’, version) }); }})4. 客户端消息// 监听serviceWorker 消息navigator.serviceWorker.addEventListener(‘message’, function (event) { // 接受数据, console.log(‘页面接受的数据:’, event);});// 发送消息document.getElementById(‘sendMSG’).addEventListener(‘click’, function () { console.log(‘绑定点击事件,点击后发送数据’); navigator.serviceWorker.controller.postMessage(‘嘀嘀嘀’);});5. 应用案例1. 拦截图片加载失败 返回默认图片 案例 https://bitsofco.de/handling-broken-images-with-service-worker/2. 蓝湖 https://lanhuapp.com/ ### 新鲜货1. https://github.com/jiahaog/nativefier#how-it-works 3. https://imgcook.taobao.org/project ...

January 16, 2019 · 2 min · jiezi

JavaScript 是如何工作的:Service Worker 的生命周期及使用场景

这是专门探索 JavaScript 及其所构建的组件的系列文章的第8篇。如果你错过了前面的章节,可以在这里找到它们:JavaScript是如何工作的:引擎,运行时和调用堆栈的概述!JavaScript是如何工作的:深入V8引擎&编写优化代码的5个技巧!JavaScript如何工作:内存管理+如何处理4个常见的内存泄漏 !JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!JavaScript是如何工作: 深入探索 websocket 和HTTP/2与SSE +如何选择正确的路径!JavaScript是如何工作的:与 WebAssembly比较 及其使用场景 !JavaScript是如何工作的:Web Workers的构建块+ 5个使用他们的场景你可能已经知道,渐进式Web应用程序 只会越来越受欢迎,因为它们的目标是让Web应用程序用户体验更流畅,创建类似于原生应用程序的体验,而不是浏览器的外观和感觉。构建渐进式Web应用程序的主要要求之一是使其在网络和加载方面非常可靠——它应该在不确定或不存在的网络条件下可用。在这篇文章中,将深入探讨 Service Workers:它们是如何工作,你应该关心什么。最后,还列出了 Service Workers 中的一些独特优点在哪些场景下是值得我们使用的。简介如果你还想了解更多 Service Workers 的知识,可以阅读作者关于 Web Workers 的文章。Service Worker是什么MDN 的介绍:Service Worker 是一个浏览器背后运行的脚步,独立于 web 页面,为无需一个页面或用户交互的功能打开了大门。今日,它包含了推送通知和背景异步(push notifications and background sync)的功能。将来,Service Worker 将支持包括 periodic sync or geofencing 的功能。基本上,Service Worker 是 Web Worker 的一个类型,更具体地说,它像 Shared Worker:Service Worker 在其自己的全局上下文中运行它没有绑定到特定的网页它不能访问到 DOM这是一个令人兴奋的 API 的原因是它允许你支持离线体验,让开发人员完全控制体验。Service Worker 的生命周期Service Worker 的生命周期与 web 页面完全分离。它包括以下几个阶段:下载安装激活下载这是浏览器下载包含 Service Worker 的 .js 文件的时候。安装要为 web 应用程序安装 Service Worker,必须先注册它,这可以在 JavaScript 代码中完成。注册 Service Worker 后,它会提示浏览器在后台启动 Service Worker 安装步骤。通过注册 Service Worker,你可以告诉浏览器你的 Service Worker 的 JavaScript 文件的位置。看看下面的代码:上例代码首先检查当前环境中是否支持 Service Worker API。如果支持,则 /sw.js 这个 Service Worker 就被注册了。每次页面加载时都可以调用 register() 方法,浏览器会判断 Service Worker 是否已经注册,根据注册情况会对应的给出正确处理。register() 方法的一个重要细节是 Service Worker 文件的位置。在本例中,可以看到 Service Worker 文件位于域的根目录,这意味着 Service Worker 范围将是这个域下的。换句话说,这个 Service Worker 将为这个域中的所有内容接收 fetch 事件。如果我们在 /example/sw.js 注册 Service Worker 文件,那么 Service Worker 只会看到以 /example/ 开头的页面的 fetch 事件(例如 /example/page1/、/example/page2/)。通常在安装步骤中,你需要缓存一些静态资源。 如果所有文件都缓存成功,则 Service Worker 将被安装。 如果任何文件无法下载和缓存,则安装步骤将失败,Service Worker 将不会激活(即不会被安装)。 如果发生这种情况,不要担心,下次再试一次。 但是,这意味着如果它安装,你知道你有这些静态资源在缓存中。如果注册需要在加载事件之后发生,这就解答了你“注册是否需要在加载事件之后发生”的疑惑。这不是必要的,但绝对是推荐的。为什么?让我们考虑用户第一次访问你的 Web 应用程序。目前还没有 Service Worker,而且浏览器无法预先知道最终是否会安装 Service Worker。如果安装了 Service Worker,浏览器将需要为这个额外的线程花费额外的 CPU 和内存,否则浏览器将把这些额外的 CPU 和内存用于呈现 Web 页面。最重要的是,如果在页面上安装一个 Service Worker,就可能会有延迟加载和渲染的风险 —— 而不是尽快让你的用户可以使用该页面。注意,这种情况对第一次的访问页面时才会有。后续的页面访问不会受到 Service Worker 安装的影响。一旦 Service Worker 在第一次访问页面时被激活,它就可以处理加载/缓存事件,以便后续访问 Web 应用程序。这一切都是有意义的,因为它需要准备好处理受限的的网络连接。激活安装 Service Worker 之后,下一步将是激活它,这是处理旧缓存管理的好机会。在激活步骤之后,Service Worker 将控制所有属于其范围的页面,尽管第一次注册 Service Worker 的页面将不会被控制,直到再次加载。Service Worker 一旦掌控,它将处于以下两种状态之一:处理从网页发出网络请求或消息时发生的提取和消息事件Service Worker 将被终止以节省内存Service Worker 生命周期如下:Service Worker 安装的内部机制在页面启动注册过程之后,看看 Service Worker 脚本中发生了什么,它通过向 Service Worker 实例添加事件监听来处理 install 事件:以下是处理安装事件时需要采取的步骤:开启一个缓存缓存我们的文件确认是否缓存了所有必需的资源对于最基本的示例,你需要为安装事件定义回调并决定要缓存哪些文件。self.addEventListener(‘install’, function(event) { // Perform install steps });下面是 Service Worker 简单的一个内部安装过程:从上例代码可以得到:调用了caches.open() 和我们想要的缓存名称, 之后调用 cache.addAll() 并传入文件数组。 这是一个promise 链( caches.open() 和 cache.addAll() )。 event.waitUntil() 方法接受一个承诺,并使用它来知道安装需要多长时间,以及它是否成功。如果成功缓存了所有文件,那么将安装 Service Worker。如果其中的一个文件下载失败,那么安装步骤将失败。这意味着需要小心在安装步骤中决定要缓存的文件列表,定义一长串文件将增加一个文件可能无法缓存的机会,导致你的 Service Worker 没有得到安装。处理 install 事件完全是可选的,你可以避免它,在这种情况下,你不需要执行这里的任何步骤。运行时缓存请求安装了 Service Worker 后,用户导航到另一个页面或刷新所在的页面,Service Worker 将收到 fetch 事件。下面是一个例子,演示如何返回缓存的资源或执行一个新的请求,然后缓存结果:上述流程:在这里我们定义了 fetch 事件,在 event.respondWith() 中,我们传递了一个来自 caches.match() 的 promise。 此方法查看请求,并查找来自 Service Worker 创建的任何缓存的任何缓存结果。如果在缓存中,响应内容就被恢复了。否则,将会执行 fetch。检查状态码是不是 200,同时检查响应类型是 basic,表明响应来自我们最初的请求。在这种情况下,不会缓存对第三方资源的请求。响应被缓存下来如果通过检查,克隆响应。这是因为响应是 Stream,所以只能消耗一次。既然要返回浏览器使用的响应,并将其传递给缓存使用,就需要克隆它,以便可以一个发送到浏览器,一个发送到缓存。更新 Service Worker当用户访问你的 Web 应用程序时,浏览器试图重新下载包含 Service Worker 代码的 .js 文件,这是在后台完成的。如果现在下载的 Service Worker 的文件与当前 Service Worker 的文件相比如果有一个字节及以上的差异,浏览器将假设 Service Worker 文件已改过,浏览器就会启动新的 Service Worker。新的 Service Worker 将启动并且安装事件将被移除。然而,在这一点上,旧的 Service Worker 仍在控制你的 web 应用的页面,这意味着新的 Service Worker 将进入 waiting 状态。一旦你的 Web 应用程序当前打开的页面被关闭,旧的 Service Worker 将被浏览器杀死,新 Service Worker 接管了控制权,它的激活事件将被激发为什么需要这些?为了避免 Web 应用程序的两个版本同时在不同的 tab 上运行的问题——这在 Web 上是非常常见的,并且可能会产生非常严重的bug(例如,在浏览器中本地存储数据时使用不同的模式)。从缓存中删除数据在激活回调中发生的一个常见任务是缓存管理。你要在激活回调中这样做的原因是,如果你要在安装步骤中清除所有旧的缓存,任何保留所有当前页面的旧 Service Worker 将会突然停止服务来自该缓存的文件。这里提供了一个如何从缓存中删除一些不在白名单中的文件的例子(在本例中,有 page-1、page-2 两个实体):要求 HTTPS 的原因在构建 Web 应用程序时,通过 localhost 使用 Service Workers,但是一旦将其部署到生产环境中,就需要准备好 HTTPS( 这是使用HTTPS 的最后一个原因)。使用 Service Worker,可以很容易被劫持连接并伪造响应。如果不使用 HTTPs,人的web应用程序就容易受到黑客的攻击。为了更安全,你需要在通过 HTTPS 提供的页面上注册 Service Worker,以便知道浏览器接收的 Service Worker 在通过网络传输时未被修改。浏览器支持浏览器对 Service Worker 的支持正在变得越来越好:Service Workers 特性将越来越完善及强大Service Workers 提供的一些独特特性包括:推送通知 — 允许用户选择从网络应用程序及时更新。后台同步 — 允许延迟操作,直到用户具有稳定的连接。通过这种方式,可以确保用户想发送的任何内容实都可以发送。定期同步(后续开放) — 提供管理定期后台同步功能的 API。Geofencing (后续开放) — 可以定义参数,也称为围绕感兴趣领域的 geofences。当设备通过geofence 时,Web 应用程序会收到一个通知,该通知允许根据用户的地理位置提供更好的体验。原文:https://blog.sessionstack.com…代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。你的点赞是我持续分享好东西的动力,欢迎点赞!一个笨笨的码农,我的世界只能终身学习!更多内容请关注公众号《大迁世界》! ...

January 3, 2019 · 2 min · jiezi