title: date: 2018-04-21 23:09:53 tags: ECMAScript
阅读原文,并写下相关要点。
你需要有在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。
if(self.IndexedDB){
console.log('IndexedDB is supported');
}
.open(dbName, versionInt) 打开数据库。传入名字和版本号。
如果数据库不存在,就创建一个。如果版本号高,数据库会用新版本,不用旧数据。 事件驱动,你懂吧。open 方法触发 success 、 error 、 upgradeneeded。像我一样:
var request = self.IndexedDB.open('EXAMPLE_DB', 1);
var db;
request.onsuccess = function(event) {
console.log('[onsuccess]', request.result);
db = event.target.result; // === request.result
};
request.onerror = function(event) {
console.log('[onerror]', request.error);
};
在回调里,event.target === request。request.result 就是结果。
onerror 里记得处理错误。
开发者工具里, Application > IndexedDB 里可以看情况。
打开新数据库或者新版本的时候,onupgradeneeded 会被触发。
request.onupgradeneeded = function(event) {
// create object store from db or event.target.result
};
createObjectStore() 创建 object store 。
db.createObjectStore(storeName, options);
storeName 也是唯一性。options 有两种属性。options.keyPath 是字段名称。(类似 Key
request.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.createObjectStore('products', {keyPath: 'id'});
};
作用如图:
如果需要 Key 自增,使用 options.autoIncrement = true 各选项组合效果如下:
IndexedDB 具有索引。Indexes 且可具有唯一约束。
store.createIndex(indexName, keyPath, options);
indexName 索引名称, keyPath 在哪个 Key 上建立。options 可选。可配置 {unique: true} 如上配置,则无法添加重复Key值得条目。
request.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.createObjectStore('products', {keyPath: 'id'});
// create unique index on keyPath === 'id'
store.createIndex('products_id_unqiue, 'id', {unique: true});
};
一旦 onupgradeneeded 事件完成, success 事件被触发。
var transaction = db.transaction(storeName, mode);
var transaction = db.transaction(storeNamesArray, mode);
根据 store 返回操作权限数量。
mode 可选 参数为 readonly readwrite versionchange。
var objectStore = transaction.objectStore(storeName);
操作成功后,complete 事件被触发。 abort() 可以回滚事务。
objectStore 是 IDBObjectStore 接口实例,提供了 get, add, clear, count, put, delete 等操作。
var request = self.IndexedDB.open('EXAMPLE_DB', 1);
request.onsuccess = function(event) {
// some sample products data
var products = [
{id: 1, name: 'Red Men T-Shirt', price: '$3.99'},
{id: 2, name: 'Pink Women Shorts', price: '$5.99'},
{id: 3, name: 'Nike white Shoes', price: '$300'}
];
// get database from event
var db = event.target.result;
// create transaction from database
var transaction = db.transaction('products', 'readwrite');
// add success event handleer for transaction
// you should also add onerror, onabort event handlers
transaction.onsuccess = function(event) {
console.log('[Transaction] ALL DONE!');
};
// get store from transaction
// returns IDBObjectStore instance
var productsStore = transaction.objectStore('products');
// put products data in productsStore
products.forEach(function(product){
var db_op_req = productsStore.add(product); // IDBRequest
});
};
for the sake of simplicity === 简单起见
var db_op_req = productsStore.add(product);
db_op_req.onsuccess = function(event) {
console.log(event.target.result == product.id); // true
};
db_op_req.onerror = function(event) {
// handle error
};
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 可关闭数据库连接。除非事务都完成,数据库才关闭。但提前关闭,则不会有新的事务产生。
var request = self.indexedDB.open('EXAMPLE_DB', 1);
request.onsuccess = function(event) {
// some sample products data
var products = [
{id: 1, name: 'Red Men T-Shirt', price: '$3.99'},
{id: 2, name: 'Pink Women Shorts', price: '$5.99'},
{id: 3, name: 'Nike white Shoes', price: '$300'}
];
// get database from event
var db = event.target.result;
// create transaction from database
var transaction = db.transaction('products', 'readwrite');
// add success event handleer for transaction
// you should also add onerror, onabort event handlers
transaction.onsuccess = function(event) {
console.log('[Transaction] ALL DONE!');
};
// get store from transaction
var productsStore = transaction.objectStore('products');
/*************************************/
// put products data in productsStore
products.forEach(function(product){
var db_op_req = productsStore.add(product);
db_op_req.onsuccess = function(event) {
console.log(event.target.result == product.id); // true
}
});
// count number of objects in store
productsStore.count().onsuccess = function(event) {
console.log('[Transaction - COUNT] number of products in store', event.target.result);
};
// get product with id 1
productsStore.get(1).onsuccess = function(event) {
console.log('[Transaction - GET] product with id 1', event.target.result);
};
// update product with id 1
products[0].name = 'Blue Men T-shirt';
productsStore.put(products[0]).onsuccess = function(event) {
console.log('[Transaction - PUT] product with id 1', event.target.result);
};
// delete product with id 2
productsStore.delete(2).onsuccess = function(event) {
console.log('[Transaction - DELETE] deleted with id 2');
};
};
request.onerror = function(event) {
console.log('[onerror]', request.error);
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
var productsStore = db.createObjectStore('products', {keyPath: 'id'});
};
输出:
检查结果:
理解更多 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)