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 指定是否包括当前 keyupperBound 在…之上 另外接收一个 Boolean 指定是否包括当前 keybound 在…之间 另外接收两个 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 ????
...