indexedDB的原理及使用

什么是indexedDB

IndexedDB 是一种可以让你在用户的浏览器内持久化存储数据的方法。IndexedDB 为生成 Web Application提供了丰富的查询能力,使我们的应用在在线和离线时都可以正常工作。

IndexedDB 是一个事务型数据库系统,类似于基于 SQL 的 RDBMS。 然而不同的是它使用固定列表,IndexedDB 是一个基于 JavaScript 的面向对象的数据库。 IndexedDB 允许您存储和检索用键索引的对象; 可以存储 structured clone algorithm 支持的任何对象。 您只需要指定数据库模式,打开与数据库的连接,然后检索和更新一系列事务中的数据。

  • 浏览器支持:PC端:chromw11+,FF4+,IE10+ 移动端:支持不较弱。
  • Web SQL:在移动端的支持比较好,但是W3C已不再维护Web SQL。

存储结构

域名分配独立空间,一个域名下可创建多个数据库,每个DB可以创建多个对象储存空间(表),一个对象存储空间可以存储多个对象数据。

indexedDB的操作

indexedDB的主要操作围绕于:增删改、索引、事务、游标。接下来详细介绍一下关于indexedDB的用法
1. 获取indexedDB

1
2
3
4
const indexedDB = window.indexedDB
|| window.webkitIndexedDB
|| window.mozIndexedDB
|| window.msIndexedDB;

window.indexedDB对象只有一个open方法,用于打开指定的数据库

2. 打开或创建数据库

1
DBOpenRequest = window.indexedDB.open(name, version)

参数为:数据库名和版本号;数据库存在,则打开它;否则创建

DBOpenRequest对象用于处理用户对数据库的操作请求,可以通过它定义操作成功和失败的处理函数。 它有以下三个方法:

  • onupgradeneeded:在创建数据库或数据库版本变化时触发,可创建表或索引等
  • onsuccess:打开成功时触发(可以获得对象存储空间信息)
  • onerror:打开失败时触发

onupgradeneeded事件会在数据库第一次被打开时;打开数据库时指定的版本号高于当前被持久化的数据库版本号。(可通过修改版本号触发该事件),在该事件的回调里面可以对数据库添加表等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
openRequst.onupgradeneeded = (e) => {
console.log('running onupgradeneeded')
// 获得数据库实例对象
db = e.target.result
console.log(`openRequst.onupgradeneeded db.version: ${db.version}`)
// 新建一个表
// 检查这个数据库是否包含这个表/如果不包含 就创建这个表 (参数:keypath主键、是否自增)|
// 哪些字段是索引的、字段是不是唯一的
if (!(db.objectStoreNames.contains(tableName))) {
console.log('need to create the objectStore')

const objectStore = db.createObjectStore(tableName, { keyPath: 'id', autoIncrement: true })
objectStore.createIndex('name', 'name', { unique: false })
objectStore.createIndex('phone', 'phone', { unique: false })

}
}

3. 打开事务

1
const transaction= db.transaction([tableName],<mode>)

值:

  • readonly:只读
  • readwrite:可读写
  • verionchange:版本变更

transaction有以下三个方法:

  • oncomplete :当事务中的所有操作请求都被处理完成时触发
  • onerror:当事务中出现错误时触发,默认的处理方式为回滚事务
  • onabort :当事务被终止时触发

4. 从事务中获取存储空间对象objectStore

1
const objectStore= transaction.objectStore(tableName)

objectStore对象上的方法:

  • add(data):添加数据
  • get(value):查询数据
  • put(data):更新数据
  • delete(value):删除数据
  • clear():清空/删除数据库
  • index(value):获得索引值
  • openCursor():打开游标(用于遍历)

更新数据的时候可以先get获取数据,修改之后再put

1
2
3
4
5
6
var result = objStore.get("110");
result.onsuccess=function(e){
var student=e.target.result;
student.name='wwww1';
objStore.put(student);
}

result也对应有onsuccess、onerror事件

利用索引查询

1
2
3
4
5
6
7
8
9
const objStore = transaction.objectStore(tabName)
// 获得索引名
const index = objStore.index('name')
//通过索引值获取数据
var result=index.get('fr')
result.onsuccess=function(e){
var student=e.target.result
console.log(student.name+":索引查询")
}

游标遍历所有

1
2
3
4
5
6
7
8
9
10
11
const objectStore = transaction.objectStore(tableName)
// 打开游标
objectStore.openCursor().onsuccess= (e)=>{
const cursor= e.target.result
if(cursor){
console.log(cursor.value)
cursor.continue()
}else {
console.log('done with cursor')
}
}

打开游标openCursor()还可以添加两个参数(范围,排序条件),游标遍历符合条件的数据, range可以为null/不写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 范围  true不包括/false包括
// 1. 匹配等于指定键值的记录
IDBKeyRange.only(指定键值)

// 2. 匹配小于指定键值的记录
IDBKeyRange.lowerBound(指定键值, 是否不包括指定键值)

// 3. 匹配大于指定键值的记录
IDBKeyRange.upperBound(指定键值, 是否不包括指定键值)

// 4. 匹配指定范围内的记录
IDBKeyRange.bound(下限键值,上限键值,是否不包括下限键值,是否不包括上限键值

// 顺序参数
IDBCursor.NEXT,顺序循环;
IDBCursor.NEXT_NO_DUPLICATE,顺序循环且键值不重复;
IDBCursor.PREV,倒序循环;
IDBCursor.PREV _NO_DUPLICATE,倒序循环且键值不重复。

5. 关闭数据库

1
db.close()

6. 删除数据库

1
indexedDB.deleteDatabase(dbName)

总结

1. 打开数据库

indexedDB.open 接收两个参数,分别为数据库名称和版本,返回的是一个 IDBOpenDBRequest 对象。可以以 DOM 事件的方式监听它的 success 和 error 来获取到它的结果。几乎所有对 indexedDB 的异步操作都是这种以事件的方式进行,返回一个拥有结果或错误的 IDBRequest 对象。在这里,open 方法得到的结果是一个 IDBDatabase 的实例。

第二个参数是数据库的版本。版本决定了数据库的模式:存储在里面的 object store 和它们的结构。当第一次通过 open 方法打开数据库时,会触发一个 onupgradeneeded 事件,我们可以也只能在这里设置数据库模式。当数据库已经存在,而我们打开一个更高版本时,同样会触发 onupgradeneeded 事件,用来更新数据库模式。

添加处理方法

我们可以通过监听它的 success, error 以及 upgradeneeded 事件来做相应的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
request.onerror = function (event) {
// Do something with request.errorCode!
console.error('open request failed',event.target.error)
}
request.onsuccess = function (event) {
// Do something with request.result!
// console.log('open request success', event)
var db = event.target.result
db.onerror = function (e) {
console.error('Database error: ', e.target.error)
}
db.onclose = e => {
console.error('Database close:', e.target.error)
}
}

可以在 success 事件里面拿到 db 对象,这个是后续操作的主体。

错误处理

由于是基于 DOM 事件模式的,所以所有的错误是会冒泡的。也就是说在一个特定 request 的上引发的错误会依次冒泡到事务,然后到 db 对象。
如果为了简化错误处理,可以直接在 db 对象上添加错误处理:

1
2
3
4
db.onerror = function (e) {
// 可以处理这个数据库上所有的错误
console.error('Database error: ', e.target.error)
}

2. 创建或更新数据库版本

当创建或者增大数据库版本的时候,会触发 onupgradeneeded 事件。在事件内部,可以拿到 db 对象来创建或更新 object store , 具体如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
request.onupgradeneeded = function (event) {
/**
* NOTE:
* 1. 创建新的 objectStore
* 2. 删除旧的不需要的 objectStore
* 3. 如果需要更新已有 objectStore 的结构,需要先删除原有的 objectStore ,然后重新创建
*/
// The IDBDatabase interface
console.log('onupgradeneeded', event)
var db = event.target.result
// Create an objectStore for this database
const objectStore = db.createObjectStore(OB_NAMES.UseKeyPath, {
keyPath: 'time'
})
}

3. 构造数据库

indexedDB 是以对象存储(object store)而不是以表结构存储的,一个数据库可以存储任意多个存储对象。每当有一个值存储在 object store 里面,就必须和一个 key 关联起来。有几种提供 key 的方法,取决于 object store 使用 key path 还是 key generator

4. 事务

所有对数据库的操作都是建立在事务 (transaction) 上的,有三种模式(mode):readonly, readewrite, versionchange.
要修改数据库结构的 schema ,必须在 versionchange 模式下。读取和修改对应另外两种模式。
通过 IDBDatabase.transaction 打开一个 transaction, 接收两个参数:storeNames, mode.

加速事务操作:
当定义作用域时(scope), 只定义需要的 object stores. 这样,就可以在不重叠的作用域上并行的执行多个事务。
只有在需要的时候才开启一个 readwrite 事务。因为在重叠的作用域上可以并发执行多个 readonly 事务,但只能有一个 readwrite 事务。