背景介绍最近实战了 Service Worker(以下简称“sw”)来进行网站缓存,以实现离线状态下,网站仍然可以正常使用。

尤其对于个人博客这种以内容为主体的静态网站,离线访问和缓存优化尤其重要;并且 Ajax 交互较少,离线访问和缓存优化的实现壁垒因此较低。

专注前端与算法的系列干货分享,转载请声明出处:原文链接: xxoo521.com

环境准备虽然 sw 要求必须在 https 环境下才可以使用,但是为了方便开发者,通过localhost或者127.0.0.1也可以正常加载和使用。

利用 cnpm 下载http-server:npm install http-server -g

进入存放示例代码的文件目录,启动静态服务器:http-server -p 80

最后,准备下 html 代码:

代码语言:javascript复制

Documenttitle></p> <p>head></p> <p><body></p> <p><img src="./image.png" height="300" width="300" /></p> <p><img</p> <p>src="https://user-gold-cdn.xitu.io/2017/10/4/50e8f96bbcb3bc644a083a409ce0ce2d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"</p> <p>/></p> <p><h3>一些提示信息sdfsfh3></p> <p><ul></p> <p><li>浏览器是否支持:<span id="isSupport">span>li></p> <p><li>service worker是否注册成功:<span id="isSuccess">span>li></p> <p><li>当前注册状态:<span id="state">span>li></p> <p><li>当前service worker状态:<span id="swState">span>li></p> <p>ul></p> <p><script src="/script.js">script></p> <p>body></p> <p>html>注册 Service Worker我们通过script.js来判断浏览器是否支持 serviceWorker,并且加载对应的代码。script.js内容如下:</p> <p>代码语言:javascript复制window.addEventListener("load", event => {</p> <p>// 判断浏览器是否支持</p> <p>if ("serviceWorker" in navigator) {</p> <p>console.log("支持");</p> <p>window.navigator.serviceWorker</p> <p>.register("/sw.js", {</p> <p>scope: "/"</p> <p>})</p> <p>.then(registration => {</p> <p>console.log("注册成功");</p> <p>})</p> <p>.catch(error => {</p> <p>console.log("注册失败", error.message);</p> <p>});</p> <p>} else {</p> <p>console.log("不支持");</p> <p>}</p> <p>});注册时机如上所示,最好在页面资源加载完成的事件(window.onload)之后注册 serviceWorker 线程。因为 serviceWorker 也会浪费资源和网络 IO,不能因为它而影响正常情况下(网络信号 ok 的情况)的使用体验。</p> <p>拦截作用域之后,我们需要用 serviceWorker 线程来拦截资源请求,但不是所有的资源都能被拦截,这主要是看 serviceWorker 的作用域:它只管理其路由和子路由下的资源文件。</p> <p>例如上面代码中,/sw.js是 serviceWorker 脚本,它拦截根路径下的所有静态资源。如果是/static/sw.js,就只拦截/static/下的静态资源。</p> <p>开发者也可以通过传递scope参数,来指定作用域。</p> <p>Service Worker 最佳实践笔者爬了很久的坑,中途看了很多人的博客,包括张鑫旭老师的文章。但是实践的时候都出现了问题,直到读到了百度团队的文章才豁然开朗。</p> <p>为了让sw.js的逻辑更清晰,这里仅仅展示最后总结出来的最优代码。如果想了解更多,可以跳到本章最后一个部分《参考链接》。</p> <p>sw 的生命周期对于 sw,它的生命周期有 3 个部分组成:install -> waiting -> activate。开发者常监听的生命周期是 install 和 activate。</p> <p>这里需要注意的是:两个事件的回调监听函数的参数上都有waitUntil函数。开发者传递到它的promise可以让浏览器了解什么时候此状态完成。</p> <p>如果难理解,可以看下面这段代码:</p> <p>代码语言:javascript复制const VERSION = "v1";</p> <p>self.addEventListener("install", event => {</p> <p>// ServiceWoker注册后,立即添加缓存文件,</p> <p>// 当缓存文件被添加完后,才从install -> waiting</p> <p>event.waitUntil(</p> <p>caches.open(VERSION).then(cache => {</p> <p>return cache.addAll(["./index.html", "./image.png"]);</p> <p>})</p> <p>);</p> <p>});更新 Service Worker 代码对于缓存的更新,可以通过定义版本号的方式来标识,例如上方代码中的 VERSION 变量。但对于 ServiceWorker 本身的代码更新,需要别的机制。</p> <p>简单来说,分为以下两步:</p> <p>在 install 阶段,调用 self.skipWaiting() 跳过 waiting 阶段,直接进入 activate 阶段在 activate 阶段,调用 self.clients.claim() 更新客户端 ServiceWorker代码如下:</p> <p>代码语言:javascript复制const VERSION = "v1";</p> <p>// 添加缓存</p> <p>self.addEventListener("install", event => {</p> <p>// 跳过 waiting 状态,然后会直接进入 activate 阶段</p> <p>event.waitUntil(self.skipWaiting());</p> <p>});</p> <p>// 缓存更新</p> <p>self.addEventListener("activate", event => {</p> <p>event.waitUntil(</p> <p>caches.keys().then(cacheNames => {</p> <p>return Promise.all([</p> <p>// 更新所有客户端 Service Worker</p> <p>self.clients.claim(),</p> <p>// 清理旧版本</p> <p>cacheNames.map(cacheName => {</p> <p>// 如果当前版本和缓存版本不一样</p> <p>if (cacheName !== VERSION) {</p> <p>return caches.delete(cacheName);</p> <p>}</p> <p>})</p> <p>]);</p> <p>})</p> <p>);</p> <p>});再探更新上一部分说了更新 sw 的 2 个步骤,但是为什么这么做呢?</p> <p>因为对于同一个 sw.js 文件,浏览器可以检测到它已经更新(假设旧代码是 sw1,新代码是 sw2)。由于 sw1 还在运行,以及默认只运行一个同名的 sw 代码,所以 sw2 处于 waiting 状态。所以需要强制跳过 waiting 状态 。</p> <p>进入 activate 后,还需要取得“控制权”,并且弃用旧代码 sw1。上方的代码顺便清理了旧版本的缓存。</p> <p>资源拦截在代码的最后,需要监听 fetch 事件,并且进行拦截。如果命中,返回缓存;如果未命中,放通请求,并且将请求后的资源缓存下来。</p> <p>代码如下:</p> <p>代码语言:javascript复制self.addEventListener("fetch", event => {</p> <p>event.respondWith(</p> <p>caches.match(event.request).then(response => {</p> <p>// 如果 Service Workder 有自己的返回</p> <p>if (response) {</p> <p>return response;</p> <p>}</p> <p>let request = event.request.clone();</p> <p>return fetch(request).then(httpRes => {</p> <p>// http请求的返回已被抓到,可以处置了。</p> <p>// 请求失败了,直接返回失败的结果就好了。。</p> <p>if (!httpRes || httpRes.status !== 200) {</p> <p>return httpRes;</p> <p>}</p> <p>// 请求成功的话,将请求缓存起来。</p> <p>let responseClone = httpRes.clone();</p> <p>caches.open(VERSION).then(cache => {</p> <p>cache.put(event.request, responseClone);</p> <p>});</p> <p>return httpRes;</p> <p>});</p> <p>})</p> <p>);</p> <p>});效果测试启动服务后,进入 localhost ,打开 devtools 面板。可以看到资源都通过 ServiceWorker 缓存加载进来了。</p> <p>现在,我们打开离线模式,</p> <p>离线模式下照样可以访问:</p> <p>最后,我们修改一下 html 的代码,并且更新一下 sw.js 中标识缓存版本的变量 VERSION:</p> <p>在第 2 次刷新后,通过上图可以看到,缓存版本内容已更新到 v2,并且左侧内容区已经被改变。</p> <p>参考链接本文全部代码地址Service Worker 生命周期百度团队:怎么使用 ServiceWorkerWeb Worker 开发模式</p> </p> <div style="text-align: left;background:#f1f1f1;padding:20px;line-height:50px;"> <a href="/6be33fdace89b7d3/f468151c42978e22.html">德国电视品牌选购指南 知名品牌如博世飞利浦西门子等常见问题与解决方案解析</a><br><a href="/66429051d260929f/44b07153622298c7.html">绝望之塔各层宝箱及BOSS在哪里介绍 绝望之塔每层攻略</a> </div> </div> </div> </div> <div id="sidebar"> <li class="widget newmodule"> <header><h3>最新发表</h3></header> <div class="main"> <ul class="menu"> <li><a title="龙之国物语:2025春季探险之旅与神秘龙族共舞" href="/6be33fdace89b7d3/6e2f85dac3473436.html">龙之国物语:2025春季探险之旅与神秘龙族共舞</a></li> <li><a title="2018俄罗斯世界杯足球场一览 萨马拉体育场" href="/6be33fdace89b7d3/c7f7db01c7688b14.html">2018俄罗斯世界杯足球场一览 萨马拉体育场</a></li> <li><a title="如何在Word中删除所有文本框" href="/6be33fdace89b7d3/51d3450e061f4a0c.html">如何在Word中删除所有文本框</a></li> <li><a title="塔防西游记:降妖除魔大作战" href="/6be33fdace89b7d3/45b28983aa2c11a5.html">塔防西游记:降妖除魔大作战</a></li> <li><a title="《残世界的鸢尾花》春日复苏庆典:探索废墟中的希望之花" href="/745e2f23d163a0d3/43d9ba65577bb4b3.html">《残世界的鸢尾花》春日复苏庆典:探索废墟中的希望之花</a></li> <li><a title="玩gta5线上应该加速哪个节点 好用的gta5游戏加速器分享" href="/745e2f23d163a0d3/e1a0507436627dc7.html">玩gta5线上应该加速哪个节点 好用的gta5游戏加速器分享</a></li> <li><a title="支持三种快充协议,三星S10+全面充电测试" href="/6be33fdace89b7d3/21917513dd00fae1.html">支持三种快充协议,三星S10+全面充电测试</a></li> <li><a title="华为运动怎么同步咕咚 华为运动与咕咚同步" href="/745e2f23d163a0d3/0ed943b0d6a93702.html">华为运动怎么同步咕咚 华为运动与咕咚同步</a></li> <li><a title="share是什么意思" href="/6be33fdace89b7d3/59321c4dae7ca992.html">share是什么意思</a></li> <li><a title="投影仪尺寸多少合适,投影仪距离与幕布尺寸对照表" href="/6be33fdace89b7d3/c5f72a24f261f23e.html">投影仪尺寸多少合适,投影仪距离与幕布尺寸对照表</a></li> </ul> </div> </li> <li class="widget divLinkage"> <header><h3>友情链接</h3></header> <div class="main"> <ul class="menu"> <script> var _mtj = _mtj || []; (function () { var mtj = document.createElement("script"); mtj.src = "https://node91.aizhantj.com:21233/tjjs/?k=gdvpk3plqch"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(mtj, s); })(); </script> </ul> </div> </li> </div> </div> <div id="footer" class="container"> <div class="copyright"> Copyright © 2022 风神世纪-科幻网游活动站 All Rights Reserved. </div> </div> </div> </div> <script type="text/javascript" src="/static/js/san_praise_sdk.js"></script> </body> </html>