引言
我们经常可以听到 Worker 这个名词,比如本文要介绍的 ServiceWorker 后面就带着一个 Worker 的单词,那么 Worker 实际上指的是什么呢?
在介绍 Worker 的定义之前,先来回忆一下浏览器的渲染原理。
在一个 tab 打开的时候,浏览器会给这个 tab 创建一个新的渲染进程(renderer process),如下图:
在每一个渲染进程中,都会有一个主线程(Main Thread),负责 JavaScript 的执行以及浏览器的渲染(JavaScript 的执行与 UI 渲染是一个互相阻塞的流程)。
如果 JavaScript 执行时间过久(比如超过 33.33 ms),那么一帧内留给 UI 渲染的时间不多,如果这时候网页有正在执行的动画,那么用户就会感受到卡顿。
10 FPS:能够达成基本的视觉残留(参考:定格动画至少需要 12 帧每秒以上)
30 FPS以下:让人感觉到明显的卡顿和不适感
30-50 FPS:因人而异
50-60 FPS:流畅
一个 Worker,指的是一个可以在后台独立执行 JavaScript 脚本。它存在于一个单独的 worker 线程,即使执行一些长任务也不会阻塞主线程响应用户操作(如鼠标点击、动画等)。
另外 Worker 也指使用
new Worker()
构造函数创建的一个对象,可以用在主线程与 worker 线程的通信
Worker 与 主线程之间可以通过 postMessage
进行通信:
worker 线程与主线程通信方式
Worker 一般有其独立的上下文:WorkerGlobalScope,其全局变量一般用 self 来表示,如果使用 window 则会报错
ServiceWorker 介绍
本文要介绍的 ServiceWorker 是一种特化的 Worker,专门来处理跟网页有关的资源(assets),在浏览器和真正的服务端之间扮演一个代理(Proxy)的角色。ServiceWorker 同时引入了缓存(Cache),可以用来存储一个网络响应。
下文会继续介绍 Cache 相关的知识。
图:ServiceWorker 中重要的几个角色
一般来说,ServiceWorker 处理的就是页面与缓存、服务器之间的关系(下文有详细介绍)。
ServiceWorker 的出现是为了解决下面的两个问题:
- 离线请求(提供类似于 App 的用户体验,类 App 的生命周期)
- 性能优化
Cache
由于 ServiceWorker 是一种特化型 Worker,它专门处理资源相关的逻辑,简单来说就是做一些缓存(但不止与此),下面先介绍一下 ServiceWorker 做缓存用到的一个底层 API:Cache
Cache 提供一个Request -> Response
的持久缓存,除非显式删除,存储在 Cache 里面的数据不会主动过期,同时也不会主动去更新,需要手动维护其更新。
图:Cache 存储的是 Request -> Response 的键值对
Cache 基础用法
一个域之内可以存在多个 Cache,可以通过一个名字来标识对应的 Cache:
可以通过 CacheStorage 来获取对应 Cache 对象,有同源策略
// caches extends CacheStorage,是 window / self 上面的一个全局变量
// 下面是通过一个 cacheName 来获取对应的缓存对象
const cache = await caches.open('hello-cache-v1');
然后可以通过 Cache.put
方法将缓存设置进去
const request = new Request('/samples/service-worker/basic/', { method: 'GET' })
const response = await fetch(request)
const cache = await caches.open('hello-cache-v1');
cache.put(request, response)
结果如下:
下一次获取的时候可以:
const request = new Request('/samples/service-worker/basic/', { method: 'GET' })
const cache = await caches.open('hello-cache-v1');
const matchResponse = await cache.match(request) // 此处可以获取上次存储的 Response
// 如果是带上路由 query 参数的形式
const request2 = new Request('/samples/service-worker/basic/?a=1', { method: 'GET' })
const matchResponse = await cache.match(request2, {