暂无菜单项

AppCache已被浏览器移除,离线缓存靠Service Worker接管

发布于
1

在浏览器地址栏中输入一个网址后,经过多次跳转就会出现“无法连接网络”的情况,在移动设备上这已经非常熟悉了。而HTML5离线缓存所要解决的就是这个环节:即使没有网络的情况下也能正常访问到已经被缓存的主要资源,并且保证基本的功能正常运行。

离线缓存靠Service Worker接管

Manifest短暂的辉煌和快速的退出

第一代HTML5离线缓存的方式叫做Application Cache或者叫AppCache,它的主要工作方式是用一个名叫manifest的文件来描述要缓存的内容:

manifest文件的内容很简单,第一行是CACHE MANIFEST,后面分别有CACHE、NETWORK和FALLBACK三个部分用来描述缓存的资源、网络上的资源以及备用页面的信息。当用户第一次打开网页的时候会按照清单去下载所有的资源并且保存在本地,在没有网络连接的情况下就会从缓存中读取数据。

这套机制曾经被广泛讨论过,但是由于其本身存在设计上的缺陷而很快暴露出来:缓存更新的逻辑无法控制,在修改文件之后如果不去改变manifest的内容的话,浏览器就不会再去下载了;当某个被列入清单中的资源出现404错误的时候,整个缓存过程都会受到影响。更加麻烦的是,清除缓存只能通过手动编辑清单并且等用户再次访问来完成,并没有提供程序化的方式来删除它。

因此主流浏览器已经不再支持Application Cache了,在Chrome 94、Firefox 84、Edge 94、Safari 16.4之后就不再使用Application Cache底层实现的方式去处理它了。到了2026年的时候,所有的稳定版浏览器都已经把manifest属性给忽略掉了,并且不会报错也不会解析这个属性,此时再继续使用window.applicationCache的话就会得到“代码存在但是没有效果”的结果。

Service Worker处理离线情况

用来代替AppCache的就是Service Worker和Cache API了,Service Worker本质上是一个独立的脚本,在后台运行中充当着浏览器和网络之间的桥梁角色,并且不会直接操作DOM元素,而是利用事件驱动的方式去拦截页面发出的所有网络请求,然后判断是使用缓存还是发起新的请求。

离线功能实现的关键在于两个方面:在install阶段提前加载重要的数据,在fetch阶段拦截请求并进行缓存匹配。

首先是安装过程:

const CACHE_NAME = ‘app-shell-v1’。

const PRECACHE_URLS=[‘/’, ‘/index.html’,’/styles/main.css’,’/scripts/app.js’]。

self.addEventListener(‘install’, (event) => {

event.waitUntil()

(CACHE_NAME).then(cache => cache.addAll(PRECACHE_URLS))。

)。

})。

cache.addAll()方法可以接受一个URL数组,并且会把这个数组中的所有资源都下载下来并存储到指定的缓存中。需要注意的是,在这一步里有一个很容易被忽视的地方:如果其中任何一个URL返回了404状态码,或者响应头中包含Cache-Control: no-store的话,那么整个预缓存的操作就会失败,Service Worker也会一直停留在安装阶段而不能被激活。因此对预缓存列表进行仔细挑选是很重要的,要保证每一条路径都能成功获取到数据。

在fetch阶段确定缓存策略时,对于静态资源(如JS、CSS、图片)使用Cache First的方式比较合适——先尝试从本地获取数据,如果本地没有的话再去请求网络;而对于HTML页面以及API接口则应该采用Network First的方法——即首先发起请求,得到响应之后再决定是否更新本地缓存:

self.addEventListener(‘fetch’, event => {

const { request } = event。

如果请求的目标是“文档”的话

event.respondWith(

fetch(request).catch((error) => caches.match(request))

);

return

}

event.respondWith

caches.match(request).then((cached) => cached || fetch(request))

);

});

用request.destination === 'document'来区分页面导航请求与普通资源请求,前者采用网络优先、失败后切换到缓存的方式,后者则采用缓存优先的方式。如果把所有的请求都无差别地放入缓存匹配中,那么POST请求以及带有凭证的接口调用就会被错误地缓存起来,造成登录状态异常或者表单提交静默失败的问题。

在激活的过程中也要对旧的缓存进行清理,在每一次更新的时候用改变缓存的名字来区分不同的版本,并且把不需要的老缓存给删掉:

event = self.addEventListener(‘activate’)

event.waitUntil(

caches.keys().then(names =>

Promise.all()

names

.filter(function(name){return name!==CACHE_NAME;})

.map((name) => caches.remove(name))

)

)

);

});

Service Worker 生效存在一个延迟:第一次访问网页的时候完成了注册、安装的过程,但是缓存逻辑只会在下一次页面加载时才起作用。如果想要马上接管的话,在等待状态下调用 skipWaiting() ,并且在激活的时候调用 clients.claim()。

部署的条件和百度收录需要注意的事项

Service Worker注册有三个硬性条件:页面要用HTTPS协议来访问(除了localhost之外),sw.js文件要放在网站根目录下或者用scope参数明确地指定它的运行范围,并且浏览器本身也要支持ServiceWorker API。如果是在HTTP域名下使用navigator.serviceWorker的话,它将会是undefined,根本无法进行注册操作。

对于以百度收录为目的的网站而言,离线缓存策略要特别注意。可以被搜索引擎索引的HTML页面不能采用Cache First的方式,否则爬虫抓取到的会是过时的内容,从而影响新内容的收录速度。正文HTML应该走网络优先,Service Worker只起到兜底的作用;静态资源如CSS、JS和图标等可以使用缓存优先来避免重复请求造成的加载延迟。

用Cache API来缓存资源的时候,所缓存的就是整个Response对象,包含状态码以及响应头等信息。因此通过缓存获取到的数据与真实的网络数据在结构上是完全一样的,并且浏览器、爬虫都不会察觉到其中的不同之处。但是要注意到的是缓存版本失控的问题——长时间不更换缓存名称的话,那么用户看到的就一直是第一次访问时所保存的老页面了。

从Manifest到Service Worker,HTML5离线缓存的技术路线已经完成了由“声明式配置”向“编程式控制”的转变。Manifest的退出并不是因为离线的需求消失了,而是因为人们对于Web应用可以随时随地使用的期望越来越高了。Service Worker提供了更加细致的拦截功能和更灵活的缓存策略,但是这也意味着开发者要掌握事件的生命周期,并且要写出更多的代码。两者之间并没有技术上的优劣之分,“已废弃”和“正在使用”之间的区别就是事实上的不同而已。

0 点赞
0 收藏
分享
0 讨论
反馈
0 / 600
0 讨论
热门最新
总结
暂无总结