阅读原文,并写下相关要点。
你需要有在Service Workers 中的 IndexedDB 的实现的 Service Workers 相关概念及知识。
开始
单页应用基本需要 web service 装在数据。数据通过控制器注入 DOM 中。大多数前端框架都这么做。
你可以缓存静态网页和资源文件,尤其是浏览的数据,但没有网络的时候,用户可以从当地数据库缓存中进行读取。
IndexedDB 是被废弃的 Web SQL 数据库的代替品。一个键值对的 NoSQL 且支持超大储存(上浮20-50%硬盘空间)的数据库。支持数据类型 number, string, JSON, blob 等等。
IndexedDB遵守同源策略。这意味着不同应用不能相互访问数据库。这是事件驱动,具有事务性,原子操作,API 异步的数据库。被所有主流浏览器支持,不支持的以后也会支持。非常适合储存离线数据。
IndexedDB 2.0 是一个值得一看的。虽然所有浏览器都不支持。
介绍
IndexedDB 由多个 object store 组成。像 MySQL 的表、 MongoDB 的集合。每个储存都有多个对象。像是 MySQL 表里的行。
object store 可以记录多行,是键值对型的。每行靠 key 上升排序。数据库里只能有一个独一无二的 object store 名字。
一个数据库创建的时候,版本为1。 数据库不能有多版本。
可以被多个客户端连接,读写操作具有事务性,
key 的类型:string, date, float, a binary blob, or an array。
value 跟 JavaScript 表达的一样:boolean, number, string, date, object, array, regexp, undefined and null。
用 IndexedDB 的基本操作:
- 打开数据库。
- 创建一个 object store。
- 事务性执行数据库操作,如添加或检索数据。
- 等操作完成,通过监听事件。
- 用读出结果坐点东西。
window 和 service worker 的全局作用域里都可以使用。
检查一下支持不,window.indexedDB or self.IndexedDB。
1 | if(self.IndexedDB){ |
.open(dbName, versionInt) 打开数据库。传入名字和版本号。
如果数据库不存在,就创建一个。如果版本号高,数据库会用新版本,不用旧数据。
事件驱动,你懂吧。open 方法触发 success 、 error 、 upgradeneeded。像我一样:
1 | var request = self.IndexedDB.open('EXAMPLE_DB', 1); |
在回调里,event.target === request。request.result 就是结果。
onerror 里记得处理错误。
开发者工具里, Application > IndexedDB 里可以看情况。
打开新数据库或者新版本的时候,onupgradeneeded 会被触发。
1 | request.onupgradeneeded = function(event) { |
createObjectStore() 创建 object store 。
1 | db.createObjectStore(storeName, options); |
storeName 也是唯一性。options 有两种属性。options.keyPath 是字段名称。(类似 Key
1 | request.onupgradeneeded = function(event) { |
作用如图:
如果需要 Key 自增,使用 options.autoIncrement = true
各选项组合效果如下:
IndexedDB 具有索引。Indexes 且可具有唯一约束。
1 | store.createIndex(indexName, keyPath, options); |
indexName 索引名称, keyPath 在哪个 Key 上建立。options 可选。可配置 {unique: true}
如上配置,则无法添加重复Key值得条目。
1 | request.onupgradeneeded = function(event) { |
一旦 onupgradeneeded 事件完成, success 事件被触发。
1 | var transaction = db.transaction(storeName, mode); |
根据 store 返回操作权限数量。
mode 可选 参数为 readonly readwrite versionchange。
1 | var objectStore = transaction.objectStore(storeName); |
操作成功后,complete 事件被触发。
abort() 可以回滚事务。
objectStore 是 IDBObjectStore 接口实例,提供了 get, add, clear, count, put, delete 等操作。
1 | var request = self.IndexedDB.open('EXAMPLE_DB', 1); |
for the sake of simplicity === 简单起见
1 | var db_op_req = productsStore.add(product); |
CRUD:
- objectStore.add(data)
- objectStore.get(key)
- objectStore.getAll()
- objectStore.count(key?)
- objectStore.getAllKeys()
- objectStore.put(data, key?)
- objectStore.delete(key)
- objectStore.clear()
详情见 IDBObjectStore
close 可关闭数据库连接。除非事务都完成,数据库才关闭。但提前关闭,则不会有新的事务产生。
1 | var request = self.indexedDB.open('EXAMPLE_DB', 1); |
输出:
检查结果:
理解更多 IndexedDB 概念,如游标,数据库迁移,数据库版本控制。
学习如何正确使用是一个大问题。遵从下面的建议去让你更好使用离线数据库:
- service worker 的 install 事件中去缓存静态文件。
- 在 service 的 activate 事件中初始化数据库比 worker 的 install 事件好。新数据库与 old service worker 混合使用可能会产生冲突。用 keyPath 作为 URL 储存点。
- 无论在线或离线,你的 app 会使用缓存文件。但获取数据的请求依然会发出。
- 当线上请求出现错误的时候,设计一个 offline-api 的地址,进入 service worker 获取数据库数据。
- 请求以 /offline-api 开头,那么使用等于请求 keyPath 从数据库提取数据, 在 application/json 之类的响应上设置适当的头并将响应返回给浏览器。 你可以使用 Response 构造函数。
用上面的方法,可以构造一个完全离线使用的应用。作者准备写一系列的文章去细讲 Progress Web Apps。
这篇文章可能漏讲一些东西,去仔细审阅文档吧。
与缓存API不同,IndexedDB API是事件驱动的,而不是基于 Promise 的。使用一些indexeddb包装库,它允许你编写基于promise的代码。
- localForage (~8KB, promises, good legacy browser support)
- IDB-keyval (500 byte alternative to localForage, for modern browsers)
- IDB-promised (~2k, same IndexedDB API, but with promises)
- Dexie (~16KB, promises, complex queries, secondary indices)
- PouchDB (~45KB (supports custom builds), synchronization)
- Lovefield (relational)
- LokiJS (in-memory)
- ydn-db (dexie-like, works with WebSQL)