浏览器数据库系统-IndexedDB

前端存储数据,我们一般会用到cookieWeb Storage,但是他们都有存储长度的限制,尤其是cookie存储内容是比较小的。如果想要大容量的存储就要用到浏览器的数据库indexedDB了,第一次听说indexedDB是在美团开源的前端监控项目中,用indexedDB存储了日志,但是并没有去研究这个项目,也没有对indexedDB进行学习,但是前段时间接手了一个项目用到了indexedDB来存储数据,所以抽点时间来学习一下。

IndexedDB介绍

IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。

IndexedDB 是一个事务型数据库系统,类似于基于 SQLRDBMS。然而,不像 RDBMS 使用固定列表,IndexedDB 是一个基于 JavaScript 的面向对象数据库。IndexedDB 允许您存储和检索用索引的对象;可以存储结构化克隆算法支持的任何对象。

IndexedDB 并不支持 SQL 查询。

特点

  • 键值对储存 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入。
  • 每一个数据都有对应的主键,主键不能重复。
  • IndexedDB 操作是异步的,不会堵塞浏览器。
  • 支持事务
  • 遵守同源策略,不能操作其他域名下的数据库。
  • 存储空间大。

关于异步问题,引自MDN:

使用 IndexedDB 执行的操作是异步执行的,以免阻塞应用程序。IndexedDB 最初包括同步和异步 API。同步 API 仅用于 Web Workers,且已从规范中移除,因为尚不清晰是否需要。但如果 Web 开发人员有足够的需求,可以重新引入同步 API

基本使用

连接数据库

直接放一段简单的代码,看一下怎么连接数据库:

1
2
3
4
5
6
7
8
9
10
11
12
const request = window.indexedDB.open('db1')

request.onerror = (event) => {
console.log('error:', event)
}

request.onsuccess = (event) => {
console.log('success:', event)
}
request.onupgradeneeded = function(event) {
console.log('upgrade:', event)
};

先解释一下代码的内容:

  • window.indexedDB.open('db1'): 我们打开一个名为 db1 的数据库,如果不存在会创建。
  • onsuccess: 数据库连接成功触发
  • onerror: 数据库连接失败触发
  • onupgradeneeded: 数据库初次连接和版本升级触发

执行上面的代码,我们会看到控制台打印了内容,并成功创建了名为 db1 的数据库,且版本为1

其他的都很好理解,但是版本是怎么控制的呢?

其实window.indexedDB.open()接受两个参数:

  • namestring,数据库的名字
  • versionnumber,数据库的版本,是一个可选参数,如果我们省略了该参数的情况下:
    • 如果数据库不存在,会创建version为1的数据库,即会触发 onupgradeneeded
    • 如果数据库已经存在了,会以当前的version打开,即不会触发 onupgradeneeded

我们可以指定一个版本号来升级数据库的版本看一下:

1
const request = window.indexedDB.open('db1', 5)

可以看一下控制台与数据库的信息:

可以发现成功打开了数据库且触发了 onupgradeneeded

如果此时我们降低版本呢?

1
const request = window.indexedDB.open('db1', 2)

好吧,出错了,数据库版本并没有被降低,我们可以看到报错信息 The requested version (2) is less than the existing version (5). ,所以并不能通过这种方式来降低数据库的版本。

一般我们会在 onupgradeneeded 中创建 ObjectStore,版本更新后并不会删除之前版本创建的 store,之前版本的 store 是依然存在的。

多次创建相同名称的 store 会抛出错误,即使数据库版本不同。

例:

1
2
3
4
5
6
7
request.onupgradeneeded = function(event) {
// 保存 IDBDataBase 接口
var db = event.target.result;

// 为该数据库创建一个对象仓库
var objectStore = db.createObjectStore("name", { keyPath: "myKey" });
};

增删改查

我们先来对数据库写入一些数据用于后边的演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const dbName = 'db1'
// 连接数据库
const request = window.indexedDB.open(dbName, 1)

// 错误处理
request.onerror = function() {}

// 假设我们要存储的数据如下
const users = [
{ id: 1, name: 'xiaoming', age: 20, idCard: '123' },
{ id: 2, name: 'xiaohong', age: 18, idCard: '456' },
{ id: 3, name: 'dahuang', age: 22, idCard: '789' }
]

// onupgradeneeded是我们唯一可以修改数据库结构的地方,可以在这里创建对象存储空间
request.onupgradeneeded = function(e) {
// 获取数据库
const db = e.target.result
// 创建对象仓库,我们选择id作为key path,因为其是唯一的。
const objectStore = db.createObjectStore('users', { keyPath: 'id' })
// 使用idCard建立索引,因为其不会重复,我们使用unique索引 [name, keyPath, options]
objectStore.createIndex('idCard', 'idCard', { unique: true })

// 使用事务的 oncomplete 事件确保在插入数据前对象仓库已经创建完毕
objectStore.transaction.oncomplete = function() {
// 需要开启一个事务才能对你的创建的数据库进行操作
const usersObjectStore = db.transaction('users', 'readwrite').objectStore('users')
users.forEach(user => {
usersObjectStore.add(user)
})
}
}

执行代码可以发现已经写入了数据:

需要开启一个事务才能对你的创建的数据库进行操作。事务来自于数据库对象,而且你必须指定你想让这个事务跨越哪些对象仓库。一旦你处于一个事务中,你就可以目标对象仓库发出请求。你要决定是对数据库进行更改还是只需从中读取数据。事务提供了三种模式:readonlyreadwriteversionchange

因为已经创建了数据库,如果不再修改版本号,是不会再次触发 onupgradeneeded事件的,所以后续的增删改查我们在 onsuccess 中获取数据库来进行演示:

增加数据使用 add 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
request.onsuccess = function(event) {
const db = event.target.result
// 通过db.transaction获取一个事务,第一个参数是 希望操作的对象存储空间的列表,只有一个也可以用字符串。 第二个参数就是事务的模式
const transaction = db.transaction(['users'], 'readwrite')
// 成功与失败的回调
transaction.oncomplete = function() {
console.log('all done')
}
transaction.onerror = function () {
console.log('error')
}

// 获取对象存储空间
const store = transaction.objectStore('users')

// 增加数据使用add方法
store.add({
id: 4,
name: 'daming',
age: 22,
idCard: '000'
})

}

修改数据使用 put 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
request.onsuccess = function(event) {
const db = event.target.result
// 通过db.transaction获取一个事务,第一个参数是 希望操作的对象存储空间的列表,只有一个也可以用字符串。 第二个参数就是事务的模式
const transaction = db.transaction(['users'], 'readwrite')
// 成功与失败的回调
transaction.oncomplete = function() {
console.log('all done')
}
transaction.onerror = function () {
console.log('error')
}

// 获取对象存储空间
const store = transaction.objectStore('users')

// 增加数据使用add方法
// store.add({
// id: 4,
// name: 'daming',
// age: 22,
// idCard: '000'
// })

// 修改数据使用put方法
store.put({
id: 2,
name: 'new name',
age: 30,
idCard: 'xxx'
})

}

删除数据使用 delete 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
request.onsuccess = function(event) {
const db = event.target.result
// 通过db.transaction获取一个事务,第一个参数是 希望操作的对象存储空间的列表,只有一个也可以用字符串。 第二个参数就是事务的模式
const transaction = db.transaction(['users'], 'readwrite')
// 成功与失败的回调
transaction.oncomplete = function() {
console.log('all done')
}
transaction.onerror = function () {
console.log('error')
}

// 获取对象存储空间
const store = transaction.objectStore('users')

// 增加数据使用add方法
// store.add({
// id: 4,
// name: 'daming',
// age: 22,
// idCard: '000'
// })

// 修改数据使用put方法
// store.put({
// id: 2,
// name: 'new name',
// age: 30,
// idCard: 'xxx'
// })

// 删除数据使用delete方法,依据key path删除
store.delete(3)
}

查询数据使用 get/getAll 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
request.onsuccess = function(event) {
const db = event.target.result
// 通过db.transaction获取一个事务,第一个参数是 希望操作的对象存储空间的列表,只有一个也可以用字符串。 第二个参数就是事务的模式
const transaction = db.transaction(['users'], 'readwrite')
// 成功与失败的回调
transaction.oncomplete = function() {
console.log('all done')
}
transaction.onerror = function () {
console.log('error')
}

// 获取对象存储空间
const store = transaction.objectStore('users')

// 增加数据使用add方法
// store.add({
// id: 4,
// name: 'daming',
// age: 22,
// idCard: '000'
// })

// 修改数据使用put方法
// store.put({
// id: 2,
// name: 'new name',
// age: 30,
// idCard: 'xxx'
// })

// 删除数据使用delete方法,依据key path删除
// store.delete(3)

// 查询数据使用get,依照key path查询
const result = store.get(2)
result.onsuccess = function(e) {
console.log(e.target.result)
}
// 查询全部使用getAll
const allResult = store.getAll()
allResult.onsuccess = function(e) {
console.log(e.target.result)
}
}

以上就是一些 indexedDB 的常用 API 的说明了,用起来吧感觉确实不优雅,感觉自己也并没有什么应用场景,所以如果有场景深入使用有更深的理解再继续更这篇帖子吧。

作者

胡兆磊

发布于

2023-06-26

更新于

2023-07-02

许可协议