The idea is that we, as browser developers, acknowledge that we are not better at web development than web developers. And as such, we shouldn't provide narrow high-level APIs that solve a particular problem using patterns we like, and instead give you access to the guts of the browser and let you do it how you want, in a way that works best for your users.
按照官方给的 Demo,Service Worker 注册的代码是放在 HTML 的最后。但是,当我尝试把 Service Worker 的注册代码提到最开头,并且 console 出时间戳,我发现一个现象:即便 Service Worker 注册成功之后再请求资源,这些资源也不会触发 fetch 请求,只有再次访问页面才会触发 fetch 事件。这是为什么呢?后来我在官方文档中找到了答案:如果你的页面加载时没有 Service Worker,那么它所依赖的其他资源请求也不会触发 fetch 事件。
The first time you load the demo, even though dog.svg is requested long after the service worker activates, it doesn't handle the request, and you still see the image of the dog. The default is consistency, if your page loads without a service worker, neither will its subresources. If you load the demo a second time (in other words, refresh the page), it'll be controlled. Both the page and the image will go through fetch events, and you'll see a cat instead.
The add() method of the Cache interface takes a URL, retrieves it, and adds the resulting response object to the given cache. The add() method is functionally equivalent to the following:
fetch(url).then(function(response) {
if (!response.ok) {
throw new TypeError('bad response status');
}
return cache.put(url, response);
})
不要试图通过改变 sw.js 的名字(如改成 sw_v2.js)来触发浏览器的更新,因为 HTML 本身会被 sw.js 缓存,而缓存的 HTML 中永远都指向 sw.js,导致浏览器无法得知 sw_v2.js 的更新。虽然,你可以像上面提到的文章:使用Service Worker做一个PWA离线网页应用 那样,再结合其他的手段来判断 HTML 的更新状态,但是会更加复杂,官方并不推荐。
you may consider giving each version of your service worker a unique URL. Don't do this! This is usually bad practice for service workers, just update the script at its current location.
The idea is that we, as browser developers, acknowledge that we are not better at web development than web developers. And as such, we shouldn't provide narrow high-level APIs that solve a particular problem using patterns we like, and instead give you access to the guts of the browser and let you do it how you want, in a way that works best for your users.
按照官方给的 Demo,Service Worker 注册的代码是放在 HTML 的最后。但是,当我尝试把 Service Worker 的注册代码提到最开头,并且 console 出时间戳,我发现一个现象:即便 Service Worker 注册成功之后再请求资源,这些资源也不会触发 fetch 请求,只有再次访问页面才会触发 fetch 事件。这是为什么呢?后来我在官方文档中找到了答案:如果你的页面加载时没有 Service Worker,那么它所依赖的其他资源请求也不会触发 fetch 事件。
The first time you load the demo, even though dog.svg is requested long after the service worker activates, it doesn't handle the request, and you still see the image of the dog. The default is consistency, if your page loads without a service worker, neither will its subresources. If you load the demo a second time (in other words, refresh the page), it'll be controlled. Both the page and the image will go through fetch events, and you'll see a cat instead.
The add() method of the Cache interface takes a URL, retrieves it, and adds the resulting response object to the given cache. The add() method is functionally equivalent to the following:
fetch(url).then(function(response) {
if (!response.ok) {
throw new TypeError('bad response status');
}
return cache.put(url, response);
})
不要试图通过改变 sw.js 的名字(如改成 sw_v2.js)来触发浏览器的更新,因为 HTML 本身会被 sw.js 缓存,而缓存的 HTML 中永远都指向 sw.js,导致浏览器无法得知 sw_v2.js 的更新。虽然,你可以像上面提到的文章:使用Service Worker做一个PWA离线网页应用 那样,再结合其他的手段来判断 HTML 的更新状态,但是会更加复杂,官方并不推荐。
you may consider giving each version of your service worker a unique URL. Don't do this! This is usually bad practice for service workers, just update the script at its current location.
浏览器缓存
传统意义上的浏览器缓存,分为强缓存和协商缓存,其共同点都是通过设置 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 的更新状态,但是会更加复杂,官方并不推荐。 you may consider giving each version of your service worker a unique URL. Don't do this! This is usually bad practice for service workers, just update the script at its current location.
出处: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 的未来,以后仍需多加关注学习。
前言
最近在翻红宝书,看到 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 的未来,以后仍需多加关注学习。