通过对 Service Worker 的系统学习,我们知道它为 Web 的离线处理提供了支持,但它仍需要配合离线存储、后台同步等技术来充分挖掘、发挥其威力。在本章节中,我们将对离线存储进行讨论,希望通过本章节的学习,大家可以掌握以下内容:
离线存储方案对比。
- IndexedDB 的基本使用。
- Cache API 的基本使用。
# 离线存储方案对比

如图所示,在
Chrome Devtools -> Application -> Storage / Cache标签下,我们可以看到不同的离线存储方案,究竟这些方案有什么差异,我们又该使用哪些方案来为 Service Worker 提供底层服务,本节将一一为大家说明。
# Cookie
Cookie 是服务器发送并保存到客户端的一小块数据,目的是为了解决无状态 HTTP 协议下无法验证两个请求来自同一会话的问题,主要特点为:
- 无法跨域访问。
- 存储数据格式单一。
- 接口同步访问。
- 存储空间过小(一般为
4 KB)。 - 生命周期一般由服务器设定,如果由浏览器生成,一般在浏览器关闭后失效。
- 每次请求都会自动携带 Cookie 信息,如果存放的数据过多,将会带来额外的性能开销。
- 无法在
Web Worker及Service Worker环境下访问。
# LocalStorage 和 SessionStorage
LocalStorage、SessionStorage 是 HTML5 引入的离线存储技术,主要特点为:
- 无法跨域访问。
- 存储数据格式单一。
- 接口同步访问。
- 存储空间相对于
Cookie有所增加(一般为5 MB)。 - 生命周期
LocalStorage为永久,SessionStorage则随着页面关闭而失效。 - 仅存在客户端中,不参与服务端通信。
- 无法在 Web Worker 及
Service Worker环境下访问。
至此,我们对
Cookie、LocalStorage以及SessionStorage进行了简单说明,由于其均无法在 Service Worker 环境下访问且 Service Worker 的离线存储要求能够存储大量、具有不同格式的数据,故上述存储方案均无法使用。它们的主要使用场景为:
- 由于每次请求都会自动携带
Cookie信息,优先使用Cookie来保存用户登录状态信息。 - 也正是由于每次请求都会自动携带
Cookie信息,除用户登录状态之外任何会话(比如购物车、游戏分数等)信息优先使用LocalStorage或SessionStorage
# Web SQL 和 IndexedDB
Web SQL、IndexedDB同样是HTML5引入的离线存储技术,主要特点为:
- 无法跨域访问。
- 可存储丰富的数据格式。
- 接口异步访问(基于事件)。
- 存储空间较大(一般不少于
250 MB)。 - 生命周期为永久。
- 仅存在客户端中,不参与服务端通信。
- 能够在
Web Worker及Service Worker环境下访问。
总的来说,
Web SQL、IndexedDB是关系型数据库、非关系型数据库在客户端的各自实现,其所支持的丰富数据格式、较大的存储空间以及可在Service Worker环境下访问的特性均符合 Service Worker 对底层技术的要求,但由于 Web SQL 已被 W3C 废弃且浏览器支持情况不甚理想,所以我们可选用 IndexedDB 作为 Service Worker 的底层技术支持。
# Application Cache
Application Cache 是 HTML5 引入的旨在提供页面离线访问能力的缓存机制,其使用步骤如下:
- 在文档的
html标签中设置manifest属性以引用manifest文件。 - 配置
manifest文件,在其中设置需要缓存的资源。 - 服务端正确配置
MIME-type。
上述可知,它仅仅提供了一套离线缓存机制,并非离线存储方案。这里之所以提及,因为这是 Web 离线处理的一次尝试,但由于存在各种各样的问题,导致它最终被废弃的命运,这也才会有后来的 Service Worker。其主要问题为:
- 缓存内容存在大小限制(一般为 5 MB)。
- 无法通过编程方式清除缓存,必须用户手动清除。
- m
anifest配置文件格式要求比较严格。 - 只要
manifest配置文件中的资源有一个缓存(更新)失败,将导致全部资源缓存(更新)失败。 - 由于会自动缓存引用了
manifest的 HTML,这就导致如果改了 HTML 内容,也需要更新版本才能更新。 - 难以实现动态缓存,且一旦出现问题,将难以进行调试。
# CacheStorage
CacheStorage 是 Service Worker 规范的一部分,因此从它出生的那一刻起便决定了它为 Service Worker 提供底层服务的使命。虽然它是 Service Worker 规范的一部分,但依旧可以脱离 Service Worker 单独使用。主要特点为:
- 无法跨域访问。
- 可存储丰富的数据格式。
- 接口异步访问(基于 Promise)。
- 存储空间较大(一般不少于 250 MB)。
- 生命周期为永久。
- 仅存在客户端中,不参与服务端通信。
- 能够在 Web Worker 及 Service Worker 环境下访问。
# 小结
通过上述对比,我们可以使用 IndexedDB 及 CacheStorage 来为 Service Worker 的离线存储提供底层服务,根据社区的经验,它们各自的适用场景为:
- 对于网址可寻址的(比如脚本、样式、图片、HTML 等)资源使用
CacheStorage。 - 其他资源则使用
IndexedDB。 - 完成了技术选型,接下来我们将对
IndexedDB及CacheStorage相关API的使用进行简单说明
# IndexedDB 的基本使用
本节我们将通过数据库及数据操作两个方面对 IndexedDB 的使用进行简单说明。
# 数据库操作
const openRequest = window.indexedDB.open('TodoList', 1);
作为使用 IndexedDB 的第一步,我们通过
window.indexedDB.open来打开名为TodoList的数据库,如果该数据库不存在或指定的版本大于当前版本,都将会触发接口返回值(此处为openRequest)的onupgradeneeded事件,我们一般在该事件的回调函数中进行存储空间的创建。比如:
openRequest.onupgradeneeded = function(event) {
const db = event.target.result;
const todosStore = db.createObjectStore('todos', { keyPath: 'id', autoIncrement : true });
todosStore.createIndex('status', 'status');
};
上例中,我们先通过
event.target.result来获取IDBDatabase实例,然后通过该实例的createObjectStore方法创建了一个名为todos的存储空间,该方法接收 2 个参数:
- 第一个参数为存储空间的名称。
- 第二个参数用来将存储对象中的某个属性设置为存储空间的
key值,其中autoIncrement指定了key值是否为自增。
通过
createObjectStore创建了存储空间后,我们通过存储空间的createIndex方法创建了一个名为status的索引,该方法接收 3 个参数:
- 第一个参数为索引的名称。
- 第二个参数指定了根据存储数据的哪一个属性来构建索引,其值可以为字符串或字符串数组。
- 第三个参数指定了该索引的一些约束,常用属性为:
{
unique: boolean, // 索引值是否唯一
/**
* 常用于包含操作,比如以下结构:
* const People = {
* name: 'Tom',
* skills: ['C++', 'JavaScript', 'Java'],
* };
* 如果想通过 `objectStore.index(’skill‘).get('Java')` 获取数据,
* 那么在创建 `skill` 索引的时候则需将 `multiEntry` 设置为 `true`
*/
multiEntry: boolean
}
至此便完成了数据库、存储空间的创建,接下来我们将对数据操作进行简要说明。
# 数据操作
openRequest.onsuccess = function(event) {
const db = event.target.result;
const transaction = db.transaction(['todos'], 'readonly');
const todosStore = transaction.objectStore('todos');
// ……
};
上例中,我们通过订阅
openRequest的onsuccess事件以便在回调函数中对数据进行操作,由于IndexedDB的数据操作都是基于事务的,因此第一步我们通过IDBDatabase的实例(此处为db)方法transaction来获得了一个只读事务,该实例方法接收2个参数:
- 第一个参数指定要操作的存储空间名称,其值可以为字符串或字符串数组。
- 第二个参数指定事务的模式,常用值为
readonly或readwrite,默认值为readonly
得到事务实例后,我们便可通过事务实例的
objectStore方法获得需要操作的存储空间,之后便可通过存储空间实例(此处为todosStore)进行数据的增、删、改、查等操作,具体细节参看 IndexedDB使用文档 或参照本章 IndexedDB 示例,此处不再阐述
# Cache API 的基本使用
掌握了
IndexedDB,接下来我们来简单了解下Cache API。
# CacheStorage 接口
我们可通过
caches来访问CacheStorage,主要接口为:
open:获取指定名称的Cache对象。keys:获取CacheStorage所有Cache对象中的Response条目键值列表。has:判断是否存在指定名称的Cache对象。delete:删除指定名称的Cache对象。match:获取指定请求所对应的Response条目(如匹配到多个,则返回第一个)。
# Cache 接口
通过上述
CacheStorage的一系列接口获取到Cache对象后,我们便可对缓存进行操作,主要接口为:
match:获取指定请求所对应的Response条目(如匹配到多个,则返回第一个)。matchAll:与match的唯一差别是该接口返回所有匹配项。add:获取指定URL的资源,将返回的Response添加到Cache对象中。addAll: 与add的唯一差别是该接口可以获取多个URL的资源,并将其依次添加到Cache对象中。put:将指定Request的Response添加到Cache对象中。delete: 删除指定Request的Response条目。keys:获取指定Request的Response条目键值列表
这里我们仅对 CacheStorage 和 Cache 的接口进行了一个简短说明,具体细节请参看 CacheStorage API 和 Cache API 或参照本章 Cache 示例,此处不再阐述。
# 总结
本章节我们首先通过对比得出了使用
IndexedDB和CacheStorage为Service Worker提供离线存储底层服务的结论,而后对它们的基本使用进行了简单说明。下一章我们将对离线处理中的后台同步进行讲解,相信通过Service Worker与这些底层服务的有效结合,我们完全可以构建出应对复杂网络状况下高可用的 Web 应用。