使用indexedDB的正确打开方式和多种使用场景
创始人
2025-05-29 01:49:02
0

针对以下场景,在不使用库的情况下,如何使用indexedDB数据库, 下面将介绍使用直接使用indexedDB的痛点, 最后给出解决方案

痛点1: 一个页面一个数据库,一张数据表

一张数据表,大家应该都能够操作,但你会发现,只是简简单单的使用api,会出现一些意外情况,怎么解决这样的意外情况呢?请看下面的小demo



Document
id:

str:

但是当你删除这张表, 刷新这个页面, 再次打开时,你就会发现不能正常进行操作了

在这里插入图片描述

Uncaught DOMException: Failed to execute ‘transaction’ on ‘IDBDatabase’: One of the specified object stores was not found.

why?
这是简单的操作,而且也经常用到,原因就是version
每次open一个数据库,如果需要操作数据表,就必须要在onupgradeneeded回调里面执行,但是只有version发生变化,才会再次调用onupgradeneeded

上面的原因就是删除表之后,改变了原来的version

下次再次打开, 就必须要重新建表,而建表的操作是需要onupgradeneeded,而onupgradeneeded回调执行,需要version再次发生变化

也就是初次建表,version为1,删除表, version为2
当再次建立表. 需要version为3,才会执行onupgradeneeded回调,重新见表
但是这个version一个变量不可以保存到在页面上,页面上的变量会随着页面刷新而被垃圾回收,
如何保存这个version下次还能再次使用且不影响下次页面重新建表

如何解决下面会讲到,先讲完所有案例会出现的意外情况先

痛点2:一个页面一个数据库,两张数据表

当存在同一个数据库,两个表时, 同时要初始化两个表
当删除表之后,下次再次打开,进行数据库增删改查操作,出错, 版本不对


Document

tableA

id:

str:

tableB

id:

str:

在这里插入图片描述

报错依然很眼熟,没有重新建表,就去使用这个表了, indexedDB中,表就是store
Uncaught DOMException: Failed to execute ‘transaction’ on ‘IDBDatabase’: One of the specified object stores was not found.

这个跟上面的错误原因是一样的
并且删除表的时候,也是报错version的原因
DOMException: Version change transaction was aborted in upgradeneeded event handler.

痛点3:一个页面多个数据库,多张表

当存在不同数据库, 不同表, 初始化不可以共用, 且数据库不同, 所以争对不同的数据库操作也是根据数据库来的
当删除表之后,下次再次打开,进行数据库增删改查操作,出错, 版本不对


Document

baseA, tableA

id:

str:

baseB, tableB

id:

str:

在这里插入图片描述

看到这里, 大家会不会觉得每次删除数据库之后, 操作就会出现问题,那不删除数据表, 是不是就没有问题了?

在这里插入图片描述

确实没有删除表, 数据就能下次继续这样操作, 那是不是就可以这样封装了呢?

等等, 还有一种情况

痛点4:初始化页面就立刻进行数据表操作

这是什么操作呢?
就是页面一初始化完成, 就立刻往数据表里面新增内容, 或者立刻查询数据表, 这个时候会出现什么情况呢?
请看下面例子:



Document
id:

str:

在这里插入图片描述

Uncaught TypeError: Cannot read properties of null (reading ‘transaction’)

报错说dataBase为null
在这里插入图片描述

我们查看一下MDN文档里面是怎么说的
https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API
在这里插入图片描述

简单的情况,看起来很复杂, 其实就是说想要直接使用, 步骤比较多, 遇到的问题也很多
我们继续看, 如何使用indexedDB
https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API/Using_IndexedDB

IndexedDB 鼓励使用的基本模式如下所示:

  1. 打开数据库。
  2. 在数据库中创建一个对象仓库(object store)。
  3. 启动一个事务,并发送一个请求来执行一些数据库操作,像增加或提取数据等。
  4. 通过监听正确类型的 DOM 事件以等待操作完成。
  5. 在操作结果上进行一些操作(可以在 request 对象中找到)

这些基本模式, 我们通过上面的例子, 也已经非常清楚和了解了

这才是核心, 所有操作都是异步的, 这也很好解释了, 上面的报错, 就是因为dataBase还未初始化完毕, 所以导致的错误
在这里插入图片描述

只有当open的success回调执行完,dataBase才算真正初始化完成

解决: 封装一个简单的工具类

实现增删改查,实现上面这多种操作,复杂操作,例如阻塞未实现

首先我们考虑一下能否使用类来实现?一个数据表对应一个对象
异步回调,需要使用到promise,使用时可以用then继续回调,或者用async和await关键字
接下来,我们考虑初始化一张表需要执行的操作
然后就可以开始封装

我们初始化一张表格,需要哪些参数:
数据库名称(baseName)
数据表名称(sheetName)
主键(keyName)
索引(indexArr 数组)
version(版本)

直接看完整封装

export default class IndexedDB {dataBase = null; // 数据库dataBaseName = null; // 数据库名dataSheetName = null; // 数据表名keyName = '' // 主键indexArr = [] // 索引version = nullconstructor({baseName,sheetName,keyName,indexArr,version}) {// baseName: 数据库名称// sheetName:数据表名称// keyName:主键// indexArr: Array 索引<索引名, 索引是否重复(为true则表示同一个项的值不能重复)>this.dataBaseName = baseNamethis.dataSheetName = sheetNamethis.keyName = keyNamethis.indexArr = indexArrthis.version = version}isReady(version) {return new Promise((resolve, reject) => {// 打开数据库const request = window.indexedDB.open(this.dataBaseName, version || this.version)request.onerror = (event) => {console.error('dataBase打开报错', event.target.error)reject(event.target.error)}request.onsuccess = (event) => {this.dataBase = event.target.resultthis.version = event.target.result.versionconsole.info('dataBase打开成功', event.target.result)resolve()}// 新建数据表request.onupgradeneeded = (event) => {this.dataBase = event.target.resultlet objectStore = nullif (!this.dataBase.objectStoreNames.contains(this.dataSheetName)) { // 不存在该数据表console.info('dataBase建表')objectStore = this.dataBase.createObjectStore(this.dataSheetName, {keyPath: this.keyName})// 新建索引this.indexArr.forEach(item => {objectStore.createIndex(item.name, item.name, {unique: item.unique || false})})}}})}// 写数据操作write(obj) {return new Promise((resolve, reject) => {// put()有则更新,无则新增const request = this.dataBase.transaction(this.dataSheetName, 'readwrite').objectStore(this.dataSheetName).put(obj)request.onsuccess = () => {console.info('write data success')resolve(this)}request.onerror = (e) => {console.error('write data fail')reject(e.target.error)}})}// 新增数据add(obj) {return new Promise((resolve, reject) => {if (this.dataBase.objectStoreNames.contains(this.dataSheetName)) { // 确定有表this.write(obj).then(() => {resolve()}).catch(err => {console.error(err);reject(err)})} else { // 如果表未成功建立// 第一步断开库连接this.dataBase.close()// 新增版本(必须新增版本,否则不会执行onupgradeneeded, 而表操作必须在onupgradeneeded回调中执行)this.version++;// 第二步// 重新建表this.isReady().then(() => {// 重新写入数据this.write(obj).then(() => {resolve()})}).catch(err => {console.error(err);reject(err)})}})}// 更新数据update(obj) {// 没有该索引就新增,有就更新return new Promise((resolve, reject) => {this.add(obj).then(() => {resolve()}).catch((e) => {reject(e)})})}// 读取数据read(key) {// throw new Error('xxxxxxxxx')return new Promise((resolve, reject) => {// throw new Error('1111111')if (!this.dataBase.objectStoreNames.contains(this.dataSheetName)) {return resolve('未建表')}const objectStore = this.dataBase.transaction([this.dataSheetName]).objectStore(this.dataSheetName)const request = objectStore.get(key)request.onerror = (e) => {console.error('read dataBase fail')reject(e.target.error)}request.onsuccess = () => {if (request.result) {resolve(request.result)} else {console.info('not find any data')resolve(null)}}})}// 删除数据remove(key) {return new Promise((resolve, reject) => {// 不管有没有这个索引,都是删除成功的回调if (!this.dataBase.objectStoreNames.contains(this.dataSheetName)) {return resolve("未建该表")}const request = this.dataBase.transaction([this.dataSheetName], 'readwrite').objectStore(this.dataSheetName).delete(key)request.onsuccess = (e) => {console.info('dataBase数据删除成功', e)resolve()}request.onerror = (e) => {console.error('dataBase数据删除事务失败')reject(e.target.error)}})}// 删除数据库deleDataBase(dataBaseName) {return new Promise((resolve, reject) => {const DBDeleteRequest = window.indexedDB.deleteDatabase(dataBaseName);this.dataBase.close();DBDeleteRequest.onerror = (event) => {console.error("Error deleting database.");reject(event.target.error)};DBDeleteRequest.onsuccess = (event) => {console.log("Database deleted successfully", event);console.log(event.result); // should be undefinedresolve(event.result)};})}// 删除表deleSheet(version) {return new Promise((resolve, reject) => {const request = window.indexedDB.open(this.dataBaseName, version);request.onerror = (event) => {// Handle errors.console.error(event.target.error)reject(event.target.error)};request.onsuccess = (event) => {this.dataBase.close();console.log('delet success')}request.onupgradeneeded = (event) => {this.dataBase = event.target.result;this.version = this.dataBase.version;this.dataBase.deleteObjectStore(this.dataSheetName);// this.dataBase.close(); //删除之后不需要再关闭,否则会报错 DOMException: The connection was closedconsole.log(event.target, 'delete success')resolve(event)};})}destroy() {this.dataBase.close();console.log('数据库连接关闭')}
}

1.为什么初始化时,不执行open数据库?
2.为什么isReady()可传参,可不传参,什么情况适合传参?
3.为什么新增和修改数据时,需要先判断是否有表?
4.为什么删除和查询,不需要再次更新版本

接下来通过demo来解释

操作一张表


操作一张表

student表

id:

str:



1.为什么初始化时,不执行open数据库?

通过这个demo,可以解释第一个疑问,没有执行open,是为了每次操作时就执行isReady()操作,也就是说只要进行数据表的增删改查再去新建这张表,这样就不存在,未建表时,进行表操作;如果这张表已经建好了,那自然而然不会执行onupgradeneeded回调
在这里插入图片描述

每次新增完这张数据表,就可以关闭数据库连接,只要不频繁的进行数据库的增删改查操作,我觉得就能关闭数据库连接,以免忘记关闭,同时也防止出现多次open数据库的情况

多次open数据库,但是没有及时close,会导致删除表操作时,卡死
删除表时,我们知道,需要版本号进行修改,且执行onupgradeneeded回调
也就是说多次open数据库,没有依次进行close,会导致真正的version发生改变时,也不执行onupgradeneeded回调

操作同一数据库不同表


同一个页面操作同一个数据库中的不同表

student表

id:

str:


teacher表

id:

str:



效果如图

在这里插入图片描述

2.为什么isReady()可传参,可不传参,什么情况适合传参?

这里可以解释这个疑问,同一个数据库中,多张表操作,会导致version版本号发生变化,所以需要页面维护这个版本号变量,当从一个表创建好之后,再创建另一个表时,需要升级版本号

所以当存在一个页面出现同一个数据库,多张表的情况就需要维护这个最高的版本号是多少了

当然,刷新页面这个版本号这个变量就会销毁,但是没关系,我们每次isReady()的时候,能够读取到最新的version变量

当一个页面操作同一个数据库中的不同表时,就需要维护version,所以也就这种情况需要传参,不然拿不到最新的version值,就会出现如下报错

dataBase打开报错 DOMException: The requested version (11) is less than the existing version (12).

并且删除数据库之后,下次依旧还能继续创建数据表再次进行数据添加和删除操作

在这里插入图片描述

操作不同数据库不同表



同一个页面操作不同数据库中的不同表

ss库中student表

id:

str:


tt库teacher表

id:

str:



在这里插入图片描述

3.为什么新增和修改数据时,需要先判断是否有表?
因为当open表的回调还未执行完成, 可能页面一初始化完成,就立刻去查询页面是否存在某个数据, 当不存在这个数据时, 再去新增这个数据, 这张情况下, 就会出现未成功建表, 可以使用dataBase.objectStoreNames.contains(this.dataSheetName)判断

如果未建表, 就去操作数据表, 肯定是会报错的, 错误如下:
DOMException: Failed to execute ‘transaction’ on ‘IDBDatabase’: One of the specified object stores was not found.

数据库未open连接报错如下:
DOMException: Failed to execute ‘transaction’ on ‘IDBDatabase’: The database connection is closing.

4.为什么删除和查询,不需要再次更新版本
能够发现, 新增和更新, 本质都是调用add方法, 而add方法中, 判断未建表时, 会重新建表

在这里插入图片描述

原因其实也很简单, 新增数据, 那说明这个数据需要新增或者更新, 那这个时候未建表, 肯定会报错, 但是如果一直未建表, 就一直不能新增数据, 这就会导致功能出错了, 所以这里做了一点特殊处理.

初始化页面进行表操作



Document

tableA

id:

str:

tableB

id:

str:

在这里插入图片描述

最后附上本文的代码仓库:

https://gitee.com/tiantianhy/indexedBD

当然如果还需要满足阻塞等更加复杂的操作,推荐使用indexedDB的库,这里我推荐idb,参考文章中有介绍idb库使用的文章

参考文章:

英文:
https://hackernoon.com/use-indexeddb-with-idb-a-1kb-library-that-makes-it-easy-8p1f3yqq

中文: https://blog.csdn.net/ioriogami/article/details/128438731
https://zh.javascript.info/indexeddb#da-kai-shu-ju-ku

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...