导语

在HTML5强势来袭时,前端存储数据的方案变的很多,有Web Storage(Local Storage、Session Storage)、Cookie、Web SQL Database、IndexedDB。
近些年Web Storage、Cookie已经用烂了,Web SQL Database也已经被废弃,唯独IndexedDB提起的很少,今天就探究一下IndexedDB使用的方式。
下面是使用 React + Webpack + ES6 完成的IndexedDB管理平台
https://zhensherlock.github.io/react-indexeddb-system/

IndexedDB

IndexedDB是一种可以让开发者将数据保存在客户端中,保存的数据可以被查询,并且可以离线使用。IndexedDB对于那些需要存储大量数据,或者是需要离线使用的程序是非常有效的解决方法。
下面是IndexedDB数据库的特点:

非关系型数据库

作为WebSQL数据库的取代品,IndexedDB和WebSQL的不同点在于WebSQL是关系型数据库(比如:SQL Server,MySQL)IndexedDB是非关系型数据库(比如:mongodb)。
IndexedDB不使用SQL来操作数据库。 它属于noSQL数据库。
IndexedDB数据库使用key-value键值对储存数据,values数据可以是各式各样的结构体,也可以是文件、blobs等数据类型。

事务模式

IndexedDB的任何数据库操作行为都必须要发生在事务中,如果在事务外执行数据库操作,都会提示报错。IndexedDB事务是自动提交,而非手动提交。
事务主要用在高并发系统中,当用户在不同的标签页同时打开多个实例时,这时如果没有事务功能,这些实例就会互相影响,导致数据错乱。

异步同步

IndexedDB分别提供了同步和异步访问的API,同步API目前还没有被任何浏览器所实现,并且同步API只能在Web Workers中使用。异步API在Web Workers内部和外部都可以使用
异步API方法调用完后会继续向后执行,而不会阻塞调用线程,如果想要返回数据需要开发者提供一个回调函数来接受数据,当操作完成时,数据库会以DOM事件的方式通知你,同时事件的类型会告诉你这个操作是否成功完成。
这个和XMLHttpRequest请求是类似的。

同源(same-origin)策略

IndexedDB遵循同源(same-origin)策略,同源下,数据共享。所以你只能访问同源中存储的数据,而不能访问其他源的。域名、应用层协议和端口只要有一个不同,就是不同源

数据库的CURD

下面来看一下IndexedDB操作示例,以下示例使用了部分es6语法:

打开数据库

// 打开数据库,第一个参数是数据库名称,第二个参数是数据库版本号,主要用于变更数据库结构
var request = window.indexedDB.open('library', 1);
var dbObject = {};
// 打开数据库失败的回调函数
request.onerror = event => {
alert("不能打开数据库,错误代码: " + event.target.errorCode);
};
// 打开数据库成功的回调函数
request.onsuccess = event => {
dbObject.db = event.target.result;
};
// 请求数据库版本改变的回调函数,用于操作数据库结构
request.onupgradeneeded = event => {
...
}

操作数据库结构

// 请求数据库版本改变的回调函数,用于操作数据库结构
request.onupgradeneeded = event => {
dbObject.db = event.target.result;
// 如不存在books表结构,就创建books表
if(!dbObject.db.objectStoreNames.contains('books')) {
// 创建books表,使用isbn字段作为主键
var store = dbObject.db.createObjectStore("books", {keyPath: "isbn"});
// 创建索引,设置title字段唯一性,第一个参数是索引的名称,第二个参数是索引涉及到的字段列表,第三个参数是设置是否具有唯一性
store.createIndex("by_title", "title", {unique: true});
store.createIndex("by_author", "author");
}
// 如不存在user表结构,就创建user表
if(!dbObject.db.objectStoreNames.contains('user')) {
// 创建books表,使用使用自增字段,系统自动创建key字段
var userStore = dbObject.db.createObjectStore("user", { autoIncrement:true });
// 创建索引,设置name字段唯一性
userStore.createIndex("by_name", "name", {unique: true});
}
}

新增和修改数据

// 创建一个事务,第一个参数是该事务所涉及到的表结构,第二个参数是该事务的权限,当前是可读可写,其他两个是readonly(只读取数据时使用)、versionchange(基本不用)
let transaction = dbObject.db.transaction(['books'], "readwrite");
// 获取books表实例
let store = transaction.objectStore('books');
// 根据isbn主键进行搜索,如果没有相同主键的数据,就添加,否则就根据当前主键数值进行修改。
let request = store.put({title: title, author: author, isbn: isbn});
// 操作成功的回调函数
request.onsuccess = () => {
console.log('put complete');
};
// 操作失败的回调函数
request.onerror = (event) => {
console.log(event.target.errorCode);
};

如果只想添加可以使用add方法,当主键值相冲突时,会执行操作失败的回调函数。

let request = store.add({title: title, author: author, isbn: isbn});

删除数据

// 根据isbn主键值删除所对应的数据
dbObject.db.transaction('books', 'readwrite').objectStore('books').delete(isbn);
// 清除表中所有的数据
dbObject.db.transaction('books', 'readwrite').objectStore('books').clear();

以上方法执行的结果都必须使用onsuccess和onerror中获取。

读取数据

使用索引来查询数据的前提是你只需要一条数据,如果有多条数据满足情况,你总是得到键值最小的那个。

let store = dbObject.db.transaction('books').objectStore('books');
// by_title是索引名称,在onupgradeneeded函数中创建的。
store.index('by_title').get(title).onsuccess = (event) => {
console.log(event.target.result);
};

当我们筛选数据时需要返回多条数据的时候,我们就需要使用游标来实现。

let store = dbObject.db.transaction('books').objectStore('books');
// 根据by_author索引来使用游标。
store.index('by_author').openCursor(IDBKeyRange.only(author)).onsuccess = (event) => {
let cursor = event.target.result;
if(cursor){
let obj = cursor.value;
console.log(obj);
// 将标识标记到下一行。
cursor.continue();
}
};

上面用到的IDBKeyRange是用于添加查询条件的,相当于sql中的where从句,IDBKeyRange有以下几个方法:

1、只匹配条件为 “michael” 值

IDBKeyRange.only('michael')

2、缩小匹配范围,从 “michael” 到底部,包括 “michael”

IDBKeyRange.lowerBound('michael');

3、缩小匹配范围,从 “michael” 到底部,不包括 “michael”

IDBKeyRange.lowerBound('michael', true);

4、缩小匹配范围,从头到 “michael” ,包括 “michael”

IDBKeyRange.upperBound('michael');

5、缩小匹配范围,从头到 “michael” ,不包括 “michael”

IDBKeyRange.upperBound('michael', true);

6、缩小匹配范围,从头到 “michael” ,不包括 “michael”

IDBKeyRange.upperBound('michael', true);

7、缩小匹配范围,从 “michael” 到 “john” ,第三个参数是否不包括 “michael” ,第四个参数是否不包括 “john”

IDBKeyRange.bound('michael', 'john', true, true);

参考文献

IndexedDB
基本概念
使用 IndexedDB
Indexed Database API 2.0
HTML5本地存储——IndexedDB(一:基本使用)
HTML5本地存储——IndexedDB(二:索引)
HTML5本地存储IndexedDB基础介绍(一)-数据库的简单增删改查
HTML5本地存储IndexedDB基础介绍(二)- 游标和索引
indexedDB如何实现满足多重条件查询?