PWA 系列(三)—ndexedDB

8次阅读

共计 9341 个字符,预计需要花费 24 分钟才能阅读完成。

IDB 操作的基本步骤是

open 方法打开数据库 ????

然后是创建数据库 store 对象仓库 ????

需要注意更新数据库版本应先调用 close 方法关闭旧版数据库
需要注意创建 store 一定要在新版本数据库的 upgradeneeded 事件处理函数中创建,因为本质上他是修改数据库结构

如果对数据库进行数据操作那么需要通过事务来执行 ????。

打开数据库
window.indexedDB 是一个 IDBFactory 对象,调用对象 open 方法返回的是一个 IDBOpenDBRequest 请求,监听 success 事件,e.target.result 指向一个名为 IDBDatabase 的对象,该对象就是连接到数据库的唯一 API。需要注意的是 IDBDatabase 对象有 close、createObjectStore、deleteObjectStore、transaction 方法和 name、version 等常用属性:

下面是个例子:
function getDB(dbName) {// 1️⃣
return new Promise((resolve, reject) => {
const request = window.indexedDB.open(dbName) // 2️⃣
request.onerror = reject
request.onsuccess = function (e) {// 3️⃣
let db = e.target.result
console.log(`db ${db.name}::${db.version} open success`)
resolve(db)
}
})
}
步骤:

第一步,需要先定义一个数据库这里通过自定义的 dbName 来创建这个新的数据库
第二部,通过 open 方法打开数据库
第三部,监听 onsuccess 方法,打开成功数据库对象就在这个 request 的 result 对象里面

使用方法:
; (async function () {
+ let db = await getDB(‘DEMO’)
+ db.close()
})()
打开 Application 面板就能够看到这个数据库和存储空间了:

但目前没什么卵用 ???? 因为没有定义数据库数据结构调用还不能存储东西,这个时候我们就要更新版本号并创建数据存储的结构了
升级版本号
function getUpgradedDB(db) {// 1️⃣
return new Promise((resolve, reject) => {
console.log(`current ${db.name}::${db.version}`)
let request = window.indexedDB.open(db.name, ++db.version) // 2️⃣
request.onerror = reject
request.onupgradeneeded = function (e) {// 3️⃣
let db = e.target.result
console.log(‘db upgraded’)
resolve(db)
}
})
}
步骤:

第一步,首先获取 db 对象,(可选,方便累加现有的数据库版本号)
第二步,然后通过 open 方法传入新版本号来创建新的版本数据库(⚠️ 注意版本号始终应当为整型数值)
第三步,监听 upgradeneeded 事件,将 request 的 result 对象返回,这样我们就获得了新版本的数据库

使用方法:
; (async function () {
let db = await getDB(‘DEMO’)
+ db.close()
+ db = await getUpgradedDB(db)
})()
⚠️️ 再次提醒如果传入 db 升级数据库,一定要先 close 方法关闭旧版本数据库

现在我们数据库的版本就升级一了,这个时候就应该在升级的同时创建数据库的结构:
创建存储空间
假定我们需要存储这样的数据:
{name: ‘oli’, age: 12, email: ‘example@example.com’}
需要注意的是:

name 为 name 键的索引 不唯一
age 为 age 键的索引 不唯一
email 为主键 唯一不重复

创建存储空间需要调用 db 数据库对象的 createObjectStore 方法;该方法返回的是一个 IDBObjectStore 对象。
存储空间有以下方法和属性:

看名字基本都能明白代表的含义,详细见文档:https://developer.mozilla.org…

function initDBStructor(db, storeName, opts, indexArr) {// 1️⃣
return new Promise((resolve, reject) => {
let store = db.createObjectStore(storeName, opts) // 2️⃣
indexArr.forEach(indexObj => { // 3️⃣
store.createIndex(indexObj.name, indexObj.name, { unique: indexObj.unique})
})
store.transaction.onerror = reject
store.transaction.oncomplete = function (e) {// 4️⃣
console.log(‘db initiated’)
resolve(e.target.db)
}
})
}
步骤

首先,我们接收数据库作为参数,storeName 代表存储空间的名称,opts 参数是创建的存储空间的选项(比如设置主键等),indexArr 则代表数据库需要创建的索引(这里假定所有数据都需要创建索引)
然后通过调用数据库的 createObjectStore 方法创建存储空间
根据传入的数据结构使用 store 的 createIndex 创建索引
监听 store 事务的 complete 方法,数据库初始化完成

使用方法:
; (async function () {
let db = await getDB(‘DEMO’)
db.close()
db = await getUpgradedDB(db)
+ db = await initDBStructor(db, ‘users’, { keyPath: ’email’}, [{name: ‘name’, unique: false}, {name: ‘age’, unique: false}, {name: ’email’, unique: true}])
})()
根据数据,我们设置了主键为 email 字段,然后 name 和 age 字段 unique 为 false,email 字段 unique 属性为 true,设置完毕再去调试面板:

然后我们就可以对数据仓库进行操作了:
数据的增查改删
增加数据
function setItem(db, storeName, data) {// 1️⃣
return new Promise((resolve, reject) => {
let request = db.transaction(storeName, ‘readwrite’).objectStore(storeName).add(data) // 2️⃣
request.onerror = reject
request.onsuccess = function (e) {// 3️⃣
console.log(‘add data success’)
resolve(e.target.result)
}
})
}
步骤:

这个函数需要获取三个参数,一个是数据库对象 db,一个是 store 的名称,另外一个则是需要插入的数据
然后调用 db 的 transaction,传入 store 名称并设置操作为读写权限,然后打开存储空间并调用 add 方法插入数据
最后监听 request 的 success 事件

使用方法:
; (async function () {
let db = await getDB(‘DEMO’)
– db.close()
– db = await getUpgradedDB(db)
+ let res = await setItem(db, ‘users’, { name: ‘oli’, age: 11, email: ‘hello@example.com’})
})()
我们增加了一条数据,调试工具打开看下现在的数据库:

成功插入数据!值得庆祝 ????
查询数据
function getItem(db, storeName, key) {// 1️⃣
return new Promise((resolve, reject) => {
let request = db.transaction(storeName, ‘readonly’).objectStore(storeName).get(key) // 2️⃣
request.onerror = reject
request.onsuccess = function (e) {// 3️⃣
console.log(‘get data success’)
resolve(e.target.result)
}
})
}
步骤:

第一步,获取到 db 数据库对象,接收 store 名称以及数据 key 我们要根据 key 来做查询操作获取 value
第二步,调用数据库对象的 transaction 传入 store 名称和只读权限,然后获取存储空间并调用 get 方法 =
第三步,监听 success 事件

使用方法:
; (async function () {
let db = await getDB(‘DEMO’)
– let res = await setItem(db, ‘users’, { name: ‘oli’, age: 11, email: ‘hello@example.com’})
+ let res = await getItem(db, ‘users’, ‘hello@example.com’)
})()
返回的数据如下:

修改数据
function modifyItem(db, storeName, key, data) {// 1️⃣
return new Promise(async (resolve, reject) => {
let request = db.transaction(storeName, ‘readonly’).objectStore(storeName).get(key) // 2️⃣
request.onerror = reject
request.onsuccess = function (e) {// 3️⃣
if (e.target.result) {
let requestUpdate = db.transaction(storeName, ‘readwrite’).objectStore(storeName).put({…e.target.result, …data}) // 4️⃣
requestUpdate.onerror = reject
requestUpdate.onsuccess = function (e) {// 6️⃣
console.log(‘modify item success’)
resolve(e.target.result)
}
} else {
reject(‘not match key’)
}
}
})
}
步骤:

首先接收 db 数据库对象、store 存储空间名称以及 key 和修改后的 data
然后根据 key 进行查询操作
监听 request 的 success 事件
然后调用 put 方法将查询的数据和修改后的数据 merge 并插入到存储空间(为了方便演示,直接合并对象)
最后监听 requestUpdate 的 success 事件

数据修改成功:
; (async function () {
let db = await getDB(‘DEMO’)
– let res = await getItem(db, ‘users’, ‘hello@example.com’)
+ let res = await modifyItem(db, ‘users’, ‘hello@example.com’, { name: ‘woooh’})
})()
效果如下:

删除数据
最后再来个删除数据:
function deleteItem(db, storeName, key) {// 1️⃣
return new Promise((resolve, reject) => {
let request = db.transaction(storeName, ‘readwrite’).objectStore(storeName).delete(key) // 2️⃣
request.onerror = reject
request.onsuccess = function (e) {// 3️⃣
console.log(‘remove item success’)
resolve(e.target.result)
}
})
}
步骤:

接收参数 db 数据库对象,store 仓库名称,key
然后创建 readwrite 事务,调用存储空间的 delete 方法
监听 request 的 success 事件

删除成功:
; (async function () {
let db = await getDB(‘DEMO’)
– let res = await getItem(db, ‘users’, ‘hello@example.com’)
+ let res = await deleteItem(db, ‘users’, ‘hello@example.com’)
})()
看看效果:

empty! ????
游标
使用游标 openCursor 返回的是一个 IDBCursor 对象,监听 success 返回的 cursor 则是含有 value 的 IDBCursorWithValue 对象,两者区别:

在使用游标之前,我们先插入几条假数据:

然后实现一个通过游标获取所有数据的函数:
function getAllValue(db, storeName) {// 1️⃣
return new Promise((resolve, reject) => {
let request = db.transaction(storeName).objectStore(storeName).openCursor() // 2️⃣
let res = []
request.onerror = reject
request.onsuccess = function (e) {// 3️⃣
let cursor = e.target.result
if (cursor) {// 4️⃣
res.push(cursor.value)
cursor.continue()
} else {
resolve(res)
console.log(‘get all value finish’)
}
}
})

}
步骤:

首先函数接收两个参数,一个是 db 对象,一个是存储空间名称
然后创建一个 request 调用存储空间的 openCursor 方法
然后监听 request 的 success 事件
检测是否存在 cursor,如果是则 push 数据并调用 continue 方法继续监听 openCursor 的 success 事件遍历,否则返回所有结果

使用方法:
; (async function () {
+ let db = await getDB(‘DEMO’)
+ let res = await getAllValue(db, ‘users’)
})()
效果如下:

另外获得所有数据的数组还可以使用 getAll 方法:
function getAllData(db, storeName) {
return new Promise((resolve, reject) => {
let request = db.transaction(storeName).objectStore(storeName).getAll() // 1️⃣
request.onerror = reject
request.onsuccess = function (e) {// 2️⃣
let data = e.target.result
console.log(‘get all value finish’)
resolve(data)
}
})
}
步骤:

使用存储空间上的 getAll 方法
监听 success 事件

游标范围
另外还可以给游标 openCursor 方法传入 keyRange 来指定游标的范围,这种 keyRange 有多种类型,分别是 IDBKeyRange 的几个内置方法:

only 指定仅匹配
lowerBound 在 … 之下 另外接收一个 Boolean 指定是否包括当前 key
upperBound 在 … 之上 另外接收一个 Boolean 指定是否包括当前 key
bound 在 … 之间 另外接收两个 Boolean 指定是否包括当前两个 key

详细见下表:

function getDataByCursor(db, storeName, indexName, type, …args) {// 1️⃣
return new Promise((resolve, reject) => {
let keyRange
switch (type) {// 2️⃣
case ‘only’:
keyRange = IDBKeyRange.only(args[0])
break
case ‘lowerBound’:
keyRange = IDBKeyRange.lowerBound(args[0], args[1])
break
case ‘upperBound’:
keyRange = IDBKeyRange.upperBound(args[0], args[1])
break
case ‘bound’:
keyRange = IDBKeyRange.bound(…args)
break
default:
break
}
let request = db.transaction(storeName).objectStore(storeName).index(indexName).openCursor(keyRange) // 3️⃣
let res = []
request.onerror = reject
request.onsuccess = function (e) {// 4️⃣
let cursor = e.target.result
if (cursor) {
res.push(cursor.value)
cursor.continue()
} else {
resolve(res)
console.log(‘get all value finished’)
}
}
})
}
步骤:

首先接收几个参数 db 数据库对象,store 存储空间名称,index 索引名称以及 范围类型名称和选项
然后根据不同名称对应不同游标范围
调用 openCursor 方法并传入游标范围参数
监听 success 事件获取数据

使用方法:
; (async function () {
let db = await getDB(‘DEMO’)
let res
+ res = await getDataByCursor(db, ‘users’, ‘name’, ‘only’, ‘oli’)
+ res = await getDataByCursor(db, ‘users’, ‘name’, ‘lowerBound’, ‘oli’)
+ res = await getDataByCursor(db, ‘users’, ‘name’, ‘lowerBound’, ‘oli’, true)
+ res = await getDataByCursor(db, ‘users’, ‘name’, ‘bound’, ‘oli’, ‘troy’, false, false)
})()
另外还可以指定游标的方向,给 openCursor 方法传入第二个参数 prev 来指定使用倒序;如果想要过滤重复的记录,那么传入 IDBCursor.nextunique 或 IDBCursor.prevunique 即可;其他参数见文档:https://developer.mozilla.org…

使用索引搜索
index 方法返回一个名为 IDBIndex 的对象
IDBIndex 这个对象的属性方法见文档:https://developer.mozilla.org…

可通过索引搜索已经建立索引的条目:
function getDataByIndex(db, storeName, indexName, key) {// 1️⃣
return new Promise((resolve, reject) => {
let request = db.transaction(storeName).objectStore(storeName).index(indexName).get(key) // 2️⃣
request.onerror = reject
request.onsuccess = function(e) {// 3️⃣
let data = e.target.result
console.log(‘get value’)
resolve(data)
}
})
}

首先接收参数 db 对象、store 存储空间名称、index 索引名称和搜索关键词
然后调用存储空间的 index 方法的 get 方法搜索关键词
然后监听 success request 的 result 值即为搜索到的数据

; (async function () {
let db = await getDB(‘DEMO’)
– let res = await getAllData(db, ‘users’)
+ let res = await getDataByIndex(db, ‘users’, ‘name’, ‘troy’)
})()

另外还有在索引上调用的两种游标使用方法,见 MDN 文档:https://developer.mozilla.org…

事务和请求之间的关系
最后来聊一聊 IDB 数据库的事务和请求之间的关系:
首先,请求就是所有针对 IDB 的异步操作都会返回请求,我们监听 success 才能在请求成功之后通过 result 对象获取到的结果
然后是事务,一个请求可能有事务也有可能没有事务,比方说只连接上数据库但没有操作数据,那么就存在请求,但不存在事务
The transaction for the request. This property can be null for certain requests, such as for request returned from IDBFactory.open (You’re just connecting to a database, so there is no transaction to return).
举个例子:
; (async function () {
let getIDBRequest = window.indexedDB.open(‘DEMO’)
let db
getIDBRequest.onsuccess = e => {
db = getIDBRequest.result
debugger
}
})()
打开数据库操作就是一个请求:

这里面就没有事务
一旦操作数据,那么你就必须首先创建一个事务并设置模式,然后通过存储空间来发起请求
; (async function () {
let getIDBRequest = window.indexedDB.open(‘DEMO’)
let db
getIDBRequest.onsuccess = e => {
db = getIDBRequest.result
let transaction = db.transaction(‘users’, ‘readonly’)
let store = transaction.objectStore(‘users’)
let request = store.count()
debugger
}
})()
那么这个请求也就会包含事务了:

PWA 系列第三章 IDB 的简要介绍就到这里,这个系列的下篇文章将介绍 Service Worker ????

正文完
 0