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 ?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。