-
Notifications
You must be signed in to change notification settings - Fork 383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
浏览器缓存、CacheStorage、Web Worker 与 Service Worker #113
Comments
这几个技术结合起来就是 PWA 了 |
的确如此。 @riskers |
@youngwind 最近一年一直在关注 PWA |
这种浏览器缓存(我称之为 Header 缓存)有两个共同的缺点: “当没有网络的时候,应用无法访问,因为 HTML 页面总得去服务器获取。” 这句怎么理解,通过header设置,页面不是可以缓存下来吗 |
是,通过设置 HTML 页面的 header,的确可以将 HTML 页面也设置为强缓存。然而,要考虑下面 2 个问题。
@zhaozhiwen2014 |
@youngwind 谢谢 你文中 说的“最近在翻红宝书” 红宝书 是哪本书 求推荐 |
@zhaozhiwen2014 javascript 高级程序设计 |
结合CacheStorage+Service Worker+diff算法应该可以实现更好的前端静态资源增量更新。 |
你说的我倒是第一次听说,我在网上找了些资料:用增量更新算法为 web 应用节省流量,相关 PPT,这是你说的意思吗?当然,文中用的是 localStorage,你说的是 CacheStorage+Service Worker,不过关键的 diff 算法应该是一致的。 |
对的。我是在思考静态资源到底是合并还是拆分想到了增量更新这个想法,我也看到了腾讯的这个开源项目,另外据说也有一些cdn做了这样的事情,但是这样需要对cdn整体做改动,不掌握在项目方自己手里。 另外这种直接弃用http缓存的方案我认为可能会造成一些隐性的问题,我对CacheStorage的了解不多,应该是相当于提供了js操作http缓存的能力吧。而且结合Service Worker还可以做一些资源有效性的批量查询等。 相对于localstorage的方案,这样似乎改动更小,对原有的缓存机制也没有做太大的颠覆,想要接入或者降级应该也会更稳妥一点。 |
shaofeng 👍 这关注量 |
有个问题请教一下,浏览器本身是有http的cache control缓存的,如果这个缓存没有过期,当页面发起一个请求时,是先经过fetch拦截还是浏览器本身的http缓存? |
浏览器的缓存机制这篇文章的链接似乎已经过时,我找到了有效的链接:https://git-lt.github.io/2016/11/21/web-cache/ |
Good |
前言
最近在翻红宝书,看到 Web Worker 那章,猛然意识到,通过它竟然可以把几个缓存相关的概念串起来,甚是有趣,撰文记之。最后我也写了一个完整的离线应用 Demo,以供运行调试。
浏览器缓存
传统意义上的浏览器缓存,分为强缓存和协商缓存,其共同点都是通过设置 HTTP Header 实现。关于两者的异同已经被讨论得很多,我就不赘述了,附两个参考资料。
这种浏览器缓存(我称之为 Header 缓存)有两个共同的缺点:
应用缓存
为了在无网络下也能访问应用,HTML5 规范中设计了应用缓存(Application Cache)这么一个新的概念。通过它,我们可以做离线应用。然而,由于这个 API 的设计有太多的缺陷,被很多人吐槽,最终被废弃。废弃的原因可以看看这些讨论:
PS:我当年毕设也用到过这种技术,没想到短短几年就被废弃了,技术迭代何其之快也!
CacheStorage
为了能够精细地、可编程地控制缓存,CacheStorage 被设计出来。有了它,就可以用 JS 对缓存进行增删改查,你也可以在 Chrome 的 DevTools 里面直观地查看。对于传统的 Header 缓存,你是没法知道有哪些缓存,更加没法对缓存进行操作的。你只能被动地修改 URL 让浏览器抛弃旧的缓存,使用新的资源。
PS:CacheStorage 并非只有在 Service Worker 中才能用,它是一个全局性的 API,你在控制台中也可以访问到 caches 全局变量。
Web Worker
一直以来,一个网页只会有两个线程:GUI 渲染线程和 JS 引擎线程。即便你的 JS 写得再天花乱坠,也只能在一个进程里面执行。然而,JS 引擎线程和 GUI 渲染线程是互斥的,因此在 JS 执行的时候,UI 页面会被阻塞住。为了在进行高耗时 JS 运算时,UI 页面仍可用,那么就得另外开辟一个独立的 JS 线程来运行这些高耗时的 JS 代码,这就是 Web Worker。
Web Worker 有两个特点:
PS:还有一个相关的概念:Shared Worker,不过这个东西比较复杂,我并未深入研究,感兴趣的读者可以了解,也可以看看 Shared Worker 跟 Service Worker 的区别。
Service Worker
终于说到本文的主角了。Service Worker 与 Web Worker 相比,相同点是:它们都是在常规的 JS 引擎线程以外开辟了新的 JS 线程。不同点主要包括以下几点:
总而言之,Service Worker 是 Web Worker 进一步发展的产物。关于如何使用 Service Worker,可以参考下面的资料。
我也写了一个 Service Worker 用作离线应用的 Demo,大家可以调试观察。下面我们讨论几个 Service Worker 容易被忽略的地方,以我的 Demo 为例。
Service Worker 只是 Service Worker
一开始我以为 Service Worker 就是用来做离线应用的,后来渐渐研究才发现不是这样的。→ Service Worker 只是一个常驻在浏览器中的 JS 线程,它本身做不了什么。它能做什么,全看跟哪些 API 搭配使用。
假如把这些技术融合在一起,再加上 Manifest 等,就差不多成了 PWA 了。
总之,Service Worker 是一种非常关键的技术,有了它,我们能更接近浏览器底层,能做更多的事情。
出处:https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#handling_updates
初次访问不会触发 fetch 事件
按照官方给的 Demo,Service Worker 注册的代码是放在 HTML 的最后。但是,当我尝试把 Service Worker 的注册代码提到最开头,并且 console 出时间戳,我发现一个现象:即便 Service Worker 注册成功之后再请求资源,这些资源也不会触发 fetch 请求,只有再次访问页面才会触发 fetch 事件。这是为什么呢?后来我在官方文档中找到了答案:如果你的页面加载时没有 Service Worker,那么它所依赖的其他资源请求也不会触发 fetch 事件。
出处:https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#activate
cache.add VS cache.put
在 install 事件中用
cache.addAll
,在 fetch 事件中用cache.put
,add 和 put 有什么区别吗?→ cache.add = fetch + cache.put出处:https://developer.mozilla.org/en-US/docs/Web/API/Cache/add
event.waitUntil 和 event.respondWith
先说 event.waitUntil
cache.addAll
里面,只要有一个资源获取失败,整个 Service Worker 便会失效。再说 event.respondWith
总之,虽然 event.waitUntil 和 event.respondWith 中的 event 都是继承于 Event 类,但是它们与常见的 event 对象差异很大,这些方法也只有在 Service Worker 的那些对应的事件中才存在。
资源的更新
以前我们用强缓存的时候,如果资源需要更新,那么我们只需要改变资源的 URL,换上新的 MD5 戳就好了。如果使用 Service Worker + CacheStorage + Fetch 做离线应用,又该如何处理资源的更新呢?
当有任何的资源(HTML、JS、Image、甚至是 sw.js 本身)需要更新时,都需要改变 sw.js。因为有了 sw.js,整个应用的入口变成了 sw.js,而非原先的 HTML。每当用户访问页面时,不管你当前是不是命中了缓存,浏览器都会请求 sw.js,然后将新旧 sw.js 进行字节对比,如果不一样,说明需要更新。因此,你能看到在 Demo 中,我们有一个 VERSION 字段,它不仅代表 sw.js 本身的版本,更代表整个应用的版本。
不要试图通过改变 sw.js 的名字(如改成 sw_v2.js)来触发浏览器的更新,因为 HTML 本身会被 sw.js 缓存,而缓存的 HTML 中永远都指向 sw.js,导致浏览器无法得知 sw_v2.js 的更新。虽然,你可以像上面提到的文章:使用Service Worker做一个PWA离线网页应用 那样,再结合其他的手段来判断 HTML 的更新状态,但是会更加复杂,官方并不推荐。
出处:https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#avoid_changing_the_url_of_your_service_worker_script
每次 sw.js 的更新,都会根据 VERSION 字段新建一个缓存空间,然后把新的资源缓存在里面。等到旧的 sw.js 所控制的网页全部关闭之后,新的 sw.js 会被激活,然后 在 activate 事件中删除旧缓存空间。这样既能保证在同时打开多个网页时更新 sw.js 不出差错,也能及时删除冗余的缓存。
双重缓存
上面我们谈到,当新的 sw.js install 的时候,会重新 fetch addAll 里面的所有资源,不管里面的资源是否需要更新,这显然违背了 Web 增量下载的原则,怎么办呢? → 结合使用强缓存和 Service Worker,做一个双重缓存。强缓存在前, Service Worker 在后。举个例子,假如有两个强缓存 a_v1.js 和 b_v1.js,现在 a 不变,b 要改成 b_v2.js,修改 sw.js 的 addAll 和 VERSION。当新的 sw.js install 的时候,addAll 要 fetch a_v1.js ,但是浏览器发现 a_v1.js 是强缓存,所以根本不会发起网络请求,只有 b_v2.js 才会发起网络请求。具体的可以调试我的 Demo 查看现象。
关于这种方法,有两点要说明一下。
cache.addAll
中指定资源的版本号,就如同在 html 中指定那般。因为在使用 Service Worker 之后,HTML 只是加载资源的入口,判断资源是否改变的功能,已经转移到 sw.js 中了。总结
写到这儿,也差不多结束了,对于 Service Worker,我还有很多不懂的地方。围绕着 Service Worker 的这一系列新兴 API,代表着更好的 Web 体验,也代表着 Web 的未来,以后仍需多加关注学习。
The text was updated successfully, but these errors were encountered: