数据存储(二)WebStorage 之 IndexedDB
admin
2024-03-16 09:22:57
0

参考:《JavaScript 高级程序设计》(第3版)

Indexed Database API,简称为 IndexedDB,是在浏览器中保存结构化数据的一种数据库,支持保存、读取、查询、搜索等。

替代了已被废弃的 Web SQL Database API。

================ 使用步骤 ================

1. 建立与数据库的连接

indexedDB 是一个数据库,使用对象保存数据(不是用表来保存)。一个 indexedDB 数据库,就是一组位于相同命名空间下的对象的集合。

  • 使用 window.indexedDB.open(dbName, version) 打开数据库。
  • 使用 db.close() 关闭数据库、断开数据库连接。db代表数据库实例对象。
  • 使用 window.indexedDB.deleteDatabase(dbName) 删除数据库。(只有在数据库断开连接的情况下才能删除)
// 打开名为 JackDB 的数据库
let request = indexedDB.open("JackDB", 1);/*** onupgradeneeded 当创建一个新的数据库或者增加已存在的数据库的版本号,onupgradeneeded 事件会被触发* onupgradeneeded 是唯一可以修改数据库结构的地方*/
request.onupgradeneeded = function (event) {// todo something
};
request.onsuccess = function (event) {// event.target 是 IDBRequest 实例,指向request对象// event.target.result 指向数据库实例对象// todo something
};
request.onerror = function (event) {console.log("数据库打开失败:", event.target.errorCode);
};

2. 创建对象存储空间

因仅能在 onupgradeneeded 事件修改数据库结构,所以仅能在此事件里创建存储空间。

创建对象存储空间时,需要指定一个唯一的键。

  • 可以通过 keyPath 指定主键,
  • 可以通过 autoIncrement: true 设置自增键
request.onupgradeneeded = function (event) {// event.target 是 IDBRequest 实例,指向 request 对象db = event.target.result; // event.target.result 是数据库实例对象let store;if (!db.objectStoreNames.contains("users")) {// 创建名为 users 的存储空间// store = db.createObjectStore("users", { autoIncrement: true }); // autoIncrement 自增主键store = db.createObjectStore("users", { keyPath: "name" }); // keyPath 主键字段名}
};

3. 数据的基本CRUD

3.1 新增数据

/*** 写入数据* @param {IDBDatabase} db 数据库实例* @param {String | Array} dbName 数据库名* @param {String} storeName objectStore名* @param {*} data 待新增的数据*/
function add(db, dbName, storeName, data) {let request = db.transaction(dbName, "readwrite") // 新建事务.objectStore(storeName) // 拿到 IDBObjectStore 对象.add(data);request.onsuccess = (event) => {console.log('写入数据成功',event.target.result)};request.onerror = (event) => {console.log('写入数据失败', event.target.error);};request.onabort = (event) => {console.log('事务回滚', event.target);};
}

3.2 修改数据

/*** 根据主键修改数据(若无此主键,则新增数据)* @param {IDBDatabase} db 数据库实例* @param {String | Array} dbName 数据库名* @param {String} storeName objectStore名* @param {*} key 主键*/
function update(db, dbName, storeName, data) {let request = db.transaction(dbName, "readwrite") // 新建事务.objectStore(storeName) // 拿到 IDBObjectStore 对象.put(data);request.onsuccess = (event) => {console.log('修改数据成功',event.target.result)};request.onerror = (event) => {console.log('修改数据失败', event.target.error);};request.onabort = (event) => {console.log('事务回滚', event.target);};
}

3.3 查询数据

/*** 根据主键读取数据* @param {IDBDatabase} db 数据库实例* @param {String | Array} dbName 数据库名* @param {String} storeName objectStore名* @param {*} key 主键*/
function read(db, dbName, storeName, key) {let request = db.transaction(dbName, "readwrite") // 新建事务.objectStore(storeName) // 拿到 IDBObjectStore 对象.get(key);request.onsuccess = (event) => {console.log('读取数据成功',event.target.result)};request.onerror = (event) => {console.log('读取数据失败', event.target.error);};request.onabort = (event) => {console.log('事务回滚', event.target);};
}

3.4 删除数据

/*** 根据主键删除数据* @param {IDBDatabase} db 数据库实例* @param {String | Array} dbName 数据库名* @param {String} storeName objectStore名* @param {*} key 主键*/function remove(db, dbName, storeName, key) {let request = db.transaction(dbName, "readwrite") // 新建事务.objectStore(storeName) // 拿到 IDBObjectStore 对象.delete(key);request.onsuccess = (event) => {console.log("删除数据成功", event.target.result);};request.onerror = (event) => {console.log("删除数据失败", event.target.error);};request.onabort = (event) => {console.log("事务回滚", event.target);};
}

3.5 清空数据

/*** 清空所有数据* @param {IDBDatabase} db 数据库实例* @param {String | Array} dbName 数据库名* @param {String} storeName objectStore名*/function read(db, dbName, storeName) {let request = db.transaction(dbName, "readwrite") // 新建事务.objectStore(storeName) // 拿到 IDBObjectStore 对象.clear();request.onsuccess = (event) => {console.log("清空数据成功", event.target.result);};request.onerror = (event) => {console.log("清空数据失败", event.target.error);};request.onabort = (event) => {console.log("事务回滚", event.target);};
}

4. 游标

/*** cursor 游标* @desc 指向结果集的指针* 游标工作原理:游标指针先指向结果中的第一项,在接到查找下一项的命令时,才会指向下一项。(不提前收集结果)* 创建游标:在存储空间上调用openCursor(range, direction)* @param {IDBKeyRound} range 键范围,默认范围为全部* @param {String} direction 方向*   next 下一个(默认)*   nextunique 下一个不重复项*   prev 前一项*   prevunique 前一个不重复项* @return 会返回一个请求对象,可以指定onsuccess()/onerror()事件处理程序。*   onsuccess()可以通过event.target.result取得结果中的下一项。*     有下一项时:event.target.result 是 IDBCursorWithValue的实例*     无下一项时:event.target.result 是 null* 游标查找下一项指令:continue(key), advance(count)*   continue(key) 移动到结果集中的下一项。参数key(主键)可选。*     不指定key,游标移动到下一项;*     指定key,游标移动到指定键。*   advance(count) 向前移动count指定项数。*/

 游标的使用示例,放在最后辣 ~

5. 键范围 

/*** keyRange 键范围* @desc 利用游标查找数据太局限,键范围可灵活的查找数据(实例:IDBKeyRange)* @兼容 var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;* @func 四种定义键的方式,最后一种功能包含前三种*   1. only(key) // 只取得键为key的对象,类似于objectStore().get(key)*   2. lowerBound(startKey, isStartNext) // 指定游标开始的位置,默认isStartNext为false*        isStartNext = false  =>  从startKey开始一直查找到最后*        isStartNext = true  =>  从startKey的下一个对象开始一直查找到最后*   3. upperBound(endKey, isEndNext) // 指定游标结束的位置,默认isEndNext=false*        isEndNext = false  => 从头查找到endKey的位置*        isEndNext = true  =>  从头查找到endKey的前一个对象为止*   4. bound(startKey, endKey, isStartNext, isEndNext) // 指定游标开始和结束的位置,默认isStartNext=false, isEndNext=false*        isStartNext = false  => 从startKey开始查找*        isStartNext = true  => 从startKey的下一个对象开始查找*        isEndNext = false  => 到endKey结束*        isEndNext = true  => 到endKey的上一个对象结束*/

  键范围的使用示例,放在最后辣 ~

6. 索引

/*** 索引* @desc 类似对象存储空间。(为一个存储对象空间,指定多个键)* @意义 为提高查询速度而基于特定属性创建的。* 例如:通过用户id和用户名两种方式存储数据,就需要通过这两个键来存储记录。用户id作为主键,在为用户名创建索引。** @func 创建索引 creatIndex(indexName, indexAttrName, options)* @param indexName 索引名* @param indexAttrName 索引属性名* @param options 包含unique的配置项* @return 返回IDBIndex实例** @func 获取某个索引 index(indexName)* @param indexName 索引名* 在索引上,调用 openCursor() 来创建游标。*   此时,event.target.result.key 存储的是索引键;*        event.target.result.primaryKey 存储的是主键;*        event.target.result.value 存储的是整个对象。* 在索引上,调用 openKeyCursor() 来创建一个特殊的,只返回每条记录主键的游标。*   此时,event.target.result.key 存储的是索引键;*        event.target.result.primaryKey 存储的是主键;*        无 event.target.result.value。* 在索引上,调用 get(indexKey) 来获取整个对象。*   此时,event.target.result 即是这个对象。* 在索引上,调用 getKey(indexKey) 来通过索引键获取主键。*   此时,event.target.result 即是这个主键。** @func 获取某个存储空间下的所有索引 indexNames属性* @return 返回 DOMStringList 实例,即包含某个存储空间下所有索引名的对象** @func 删除某个索引 deleteIndex(indexName)* @return 无任何返回,因为删除索引不影响存储空间,所以无任何返回。*/

 索引的使用示例,放在最后辣 ~

7. 并发问题

/*** 并发问题* @desc IndexedDB提供的API是异步的,但是仍然存储并发问题。* @eg 例如:浏览器两个不同的标签打开同一页面,那么一个页面试图更新另一个页面尚未准备就绪的数据库,有可能引起并发问题* @eg 例如:把数据库设置为新版,也有可能引发并发问题(只有当浏览器仅有一个标签页打开使用数据库时,调用setVersion()才能完成)*     解决法一:每次成功打开数据库,都指定onversionchange事件处理程序。*             当同一个来源的另一个内标签页调用setVersion()时,会执行这个函数,在函数内部立即关掉数据库即可。*             关闭数据路方法:db.close()*     解决法二:调用setVersion()时,指定请求的onblocked()事件处理程序。*             当你想要更新数据库版本但另一个标签页已经打开数据库的情况下,就会触发这个事件。*             此时可通知用户关闭其他标签页,再重新调用setVersion()。*/

8. 限制

/*** 限制* @desc 与Web Storage类似。* @限制1 IndexedDB只能由同源页面操作,不能跨域共享信息。* @限制2 每个数据库占用磁盘空间有限。*         FireFox 4+ 的上限是每个源 50M,其他几乎都是5M* @限制3 FireFox 不允许本地文件访问IndexedDB,Chrome无限制。*/

9. 封装操作数据库的工具类

/*** IndexedDB数据库操作方法类*/
class IDBUtils {constructor(db, dbName, store) {this.db = db; // {IDBDatabase} 已被打开的数据库this.dbName = dbName; // {String | Array} 存储空间名this.store = store; // {String} 存储空间名this.type = {success: "SUCCESS", // 操作成功error: "ERROR", // 操作失败done: "DONE", // 检索结束abort: "ABORT", // 操作回滚};}/*** 新增数据* @param {*} data 要写入的数据* @param {Function} cb 回调函数*/add(data, cb) {let request = this.db.transaction(this.dbName, "readwrite") // 新建事务.objectStore(this.store) // 拿到 IDBObjectStore 对象.add(data);request.onsuccess = (event) => {if (cb) cb(this.type.success, event.target.result);};request.onerror = (event) => {if (cb) cb(this.type.error, event.target.error);};request.onabort = (event) => {if (cb) cb(this.type.abort, event.target);};}/*** 根据主键,读取数据* @param {String | Number} key 主键* @param {Function} cb 回调函数*/read(key, cb) {let request = this.db.transaction(this.dbName, "readonly") // 新建事务.objectStore(this.store).get(key);request.onsuccess = (event) => {if (cb) cb(this.type.success, event.target.result);};request.onerror = (event) => {if (cb) cb(this.type.error, event.target.error);};request.onabort = (event) => {if (cb) cb(this.type.abort, event.target);};}/*** 根据主键要么更新,要么插入* @param {*} param 一条完整的信息* @param {Function} cb 回调函数*/update(param, cb) {let request = this.db.transaction(this.dbName, "readwrite").objectStore(this.store).put(param);request.onsuccess = (event) => {if (cb) cb(this.type.success, event.target.result);};request.onerror = (event) => {if (cb) cb(this.type.error, event.target.error);};request.onabort = (event) => {if (cb) cb(this.type.abort, event.target);};}/*** 删除数据* @param {key} key 主键* @param {Function} cb 回调函数*/remove(key, cb) {let request = this.db.transaction(this.dbName, "readwrite").objectStore(this.store).delete(key);request.onsuccess = () => {if (cb) cb(this.type.success);};request.onerror = () => {if (cb) cb(this.type.error);};request.onabort = (event) => {if (cb) cb(this.type.abort, event.target);};}/*** 清空数据* @param {Function} cb 回调函数*/clear(cb) {let request = this.db.transaction(this.dbName, "readwrite").objectStore(this.store).clear();request.onsuccess = () => {if (cb) cb(this.type.success);};request.onerror = (event) => {if (cb) cb(this.type.error, event.target);};request.onabort = (event) => {if (cb) cb(this.type.abort, event.target);};}/*** 关闭数据库*/closeDB() {this.db.close();}/*** 删除数据库* @条件 在数据库没被打开的情况下,可以删除*/deleteDB() {this.db.close();let request = window.indexedDB.deleteDatabase(this.dbName);request.onsuccess = (event) => {console.log("success", event);};request.onerror = (event) => {console.log("error", event);};}// =================================================================// ============================== 游标 ==============================// =================================================================/*** 读取全部(游标)* @param {Function} cb 回调函数*/readAll(direction, cb) {let request = this.db.transaction(this.dbName).objectStore(this.store).openCursor(null, direction);let res = [];request.onsuccess = (event) => {let cursor = event.target.result; // 也可以在索引上打开 objectStore.index("id").openCursor()// cursor 有值,代表有下一项; cursor = null,代表没有下一项if (cursor) {res.push({ key: cursor.key, value: cursor.value });cursor.continue(); // 继续查找下一项} else {if (cb) cb(this.type.success, res);}};request.onerror = (event) => {if (cb) cb(this.type.error, event.target);};}/*** 利用游标更新 cursor.update()* @param {*} key 主键* @param {*} data 待更新数据* @param {Function} cb 回调函数*/cursorUpdate(key, data, cb) {let request = this.db.transaction(this.dbName).objectStore(this.store).openCursor();request.onsuccess = (event) => {let cursor = event.target.result;if (cursor) {if (cursor.key === key) {let updateRequest = cursor.update(data);updateRequest.onsuccess = (event) => {if (cb) cb(this.type.success, event.target);};updateRequest.onerror = (event) => {if (cb) cb(this.type.error, event.target);};} else {cursor.continue(); // 继续查找下一项}} else {if (cb) cb(this.type.done); // 检索结束}};request.onerror = (event) => {if (cb) cb(this.type.error, event.target);};}/*** 利用游标删除 cursor.delete()* @param {*} key 主键* @param {Function} cb 回调函数*/cursorDelete(key, cb) {let request = this.db.transaction(this.dbName).objectStore(this.store).openCursor();request.onsuccess = (event) => {let cursor = event.target.result;if (cursor) {if (cursor.key === key) {let deleteRequest = cursor.delete();deleteRequest.onsuccess = () => {if (cb) cb(this.type.success);};deleteRequest.onerror = (event) => {if (cb) cb(this.type.error, event.target);};} else {cursor.continue(); // 继续查找下一项}} else {if (cb) cb(this.type.done); // 检索结束}};request.onerror = (event) => {if (cb) cb(this.type.error, event.target);};}// ==================================================================// ============================== 键范围 =============================// ==================================================================IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;searchOnly(key, cb) {let range = IDBKeyRange.only(key);let request = this.db.transaction(this.dbName).objectStore(this.store).openCursor(range);request.onsuccess = (event) => {let cursor = event.target.result;if (cursor) {if (cb) cb({ key: cursor.key, value: cursor.value });} else {if (cb) cb(this.type.done);}};request.onerror = (event) => {if (cb) cb(this.type.error, event.target.error);};}searchLowerBound(key, isNext = false, cb) {let range = IDBKeyRange.lowerBound(key, isNext);let request = this.db.transaction(this.dbName).objectStore(this.store).openCursor(range, "prev");let res = [];request.onsuccess = (event) => {let cursor = event.target.result;if (cursor) {res.push({ key: cursor.key, value: cursor.value });cursor.continue(); // 继续查找下一项} else {if (cb) cb(this.type.success, res);}};request.onerror = (event) => {if (cb) cb(this.type.error, event.target.error);};}searchUpperBound(key, isPre = false, cb) {let range = IDBKeyRange.upperBound(key, isPre);let request = this.db.transaction(this.dbName).objectStore(this.store).openCursor(range);let res = [];request.onsuccess = (event) => {let cursor = event.target.result;if (cursor) {res.push({ key: cursor.key, value: cursor.value });cursor.continue(); // 继续查找下一项} else {if (cb) cb(this.type.success, res);}};request.onerror = (event) => {if (cb) cb(this.type.error, event.target.error);};}searchBound(startKey, endKey, isNext = false, isPre = false, cb) {let range = IDBKeyRange.bound(startKey, endKey, isNext, isPre);let request = this.db.transaction(this.dbName).objectStore(this.store).openCursor(range);let res = [];request.onsuccess = (event) => {let cursor = event.target.result;if (cursor) {res.push({ key: cursor.key, value: cursor.value });cursor.continue(); // 继续查找下一项} else {if (cb) cb(this.type.success, res);}};request.onerror = (event) => {if (cb) cb(this.type.error, event.target.error);};}// =================================================================// ============================== 索引 ==============================// =================================================================/*** 获取存储空间对象上的索引*/getIndexNames() {let indexNameList = this.db.transaction(this.dbName, "readwrite") // 新建事务.objectStore(this.store).indexNames; // 拿到 IDBObjectStore 对象console.log("indexNameList", indexNameList);}/*** 通过索引键使用游标* @param {*} indexName */getIndexCursor(indexName) {let index = this.db.transaction(this.dbName, "readwrite") // 新建事务.objectStore(this.store) // 拿到 IDBObjectStore 对象.index(indexName);let request = index.openCursor();request.onsuccess = (event) => {console.log("event", event.target.result);// 进行游标的使用、continue等逻辑};}/*** 通过索引键获取主键游标* @param {*} indexName */getIndexKeyCursor(indexName) {let index = this.db.transaction(this.dbName, "readwrite") // 新建事务.objectStore(this.store) // 拿到 IDBObjectStore 对象.index(indexName);let request = index.openKeyCursor();request.onsuccess = (event) => {console.log("event", event.target.result);// 进行游标的使用、continue等逻辑};}/*** 通过索引键获取数据* @param {*} indexName * @param {*} indexKey */formIndexGet(indexName, indexKey) {let index = this.db.transaction(this.dbName, "readwrite") // 新建事务.objectStore(this.store) // 拿到 IDBObjectStore 对象.index(indexName);let request = index.get(indexKey);request.onsuccess = (event) => {console.log("event", event.target.result);};}/*** 通过索引键获取主键* @param {*} indexName * @param {*} indexKey */fromIndexGetKey(indexName, indexKey) {let index = this.db.transaction(this.dbName, "readwrite") // 新建事务.objectStore(this.store) // 拿到 IDBObjectStore 对象.index(indexName);let request = index.getKey(indexKey);request.onsuccess = (event) => {console.log("event", event.target.result);};}
}

索引、游标、键范围的基本使用如上,当然还可以利用 索引+游标+键范围来查询数据,大家有需要可以自己封装

相关内容

热门资讯

银行间主要利率债午间走势分化 每经AI快讯,7月28日,银行间主要利率债午间走势分化,30年期国债“26超长特别国债04”收益率下...
2026海河国际消费论坛即将在... 2026海河国际消费论坛将于7月30日下午在天津启幕,目前各项筹备工作已全部就绪。本届论坛以“创新服...
原创 一... 前言 1942年的一天,一名身穿军装的年轻人,悄悄走到一位老妇人的面前。他脸上带着疲惫,眼神中却依然...
原创 曹... 八十岁的曹德旺,这几年在公开场合谈到房子这两个字,语气一次比一次冷。他早年那句"房子不过是钢筋水泥堆...
股息率近5.5%!港股红利低波... 7月28日,港股红利资产延续强势。截至13时48分,港股红利低波ETF招商(520550)涨0.46...
企业文件共享平台怎么选?主流方... 文件共享平台种类繁多,各有侧重。今天这篇文章,把2026年市面上主流的企业文件共享平台做个系统梳理,...
币圈院士:7.26以太坊(ET... 币圈院士:7.26以太坊(ETH)双周期指标暗藏方向,行情即将破位?最新行情分析参考 以太坊现价18...
老铺黄金发盈喜后股价跌16.2... 观点网讯:7月28日,老铺黄金股价裂口低开12.97%,最低见325.8港元,收盘报332港元,跌1...
苏泊尔上半年营收净利双降,法籍... 瑞财经 严明会 近日,苏泊尔(002032.SZ)披露2026年半年度业绩快报。 公告显示,公司上半...
IPO雷达|陕西瑞科回复二轮问... 深圳商报·读创客户端记者 梁佳彤 7月27日,据北交所官网,陕西瑞科新材料股份有限公司(下称“陕西瑞...
普京签令,俄军扩编 据新华社报道,俄罗斯总统普京27日签署命令, 决定组建几支军事建筑工程部队,并将俄武装力量编制总人数...
2027年德国杜塞尔多夫国际铸... 展会名称:2027年德国杜塞尔多夫国际铸造、冶金、热处理及铸件展览会GMTN 开始时间:2027-0...
郑州有了温通刮痧培训示范基地 本报讯(记者 杨振东 通讯员 张丹婧)温通刮痧是中医外治法里的一种,简单说就是在传统刮痧基础上,结合...
整箱茅台和单瓶茅台,回收行情为... 不少天津藏友存在疑惑:同样年份、同样品相的飞天茅台,整箱装和拆箱单瓶的回收报价存在差距,不清楚背后的...
北京五粮液收购需要遵循哪些通用... 北京五粮液收购的行业背景 近年来高端白酒的收藏与流通市场规模稳步扩张,北京作为国内重要的消费城市,五...
微软CEO重磅警告:只依赖一家... 来源:环球网 【环球网科技综合报道】7月28日消息,据外媒TechCrunch报道,微软CEO萨提亚...
策略师:金价夏季维持4000美... 汇通财经APP讯——今夏金价大概率在4000美元/盎司附近震荡筑底,市场等待美联储货币政策清晰指引。...
大众叙事下白酒行业周期如何拆解... 白酒行业的波动往往并非单纯由供需关系决定,而是 宏观经济预期与 渠道库存周期共振的结果。在大众认知中...
沈皓南:黄金低位大区间运行,短... 大家好,我是沈皓南,差不多有一个月没有更新黄金文章了,这段时间我去了美丽的新疆,自驾了独库公路,观赏...
原创 上... 2000年9月28日傍晚,济青高速临淄出口边一家小饭馆的木门被推开,六个山东汉子鱼贯而入。几个小时前...