Open youngwind opened 6 years ago
这几个技术结合起来就是 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 算法应该是一致的。 这种方案,有点类似于 Webpack 的热更新,不过 Webpack 的热更新只在开发环境用,不需要考虑实际环境的 Hash 等问题。 @Vevlins
对的。我是在思考静态资源到底是合并还是拆分想到了增量更新这个想法,我也看到了腾讯的这个开源项目,另外据说也有一些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 做离线应用,又该如何处理资源的更新呢?
不要试图通过改变 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 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 的未来,以后仍需多加关注学习。