写点什么

重学 JS | 一文看懂浏览器数据库 IndexedDB 详细操作

用户头像
梁龙先森
关注
发布于: 2021 年 01 月 22 日
重学JS | 一文看懂浏览器数据库IndexedDB详细操作

IndexedDB 是 Indexed Database API 的简称,是在浏览器中存储大量结构化数据(包括文件/ blob)的一种数据库,是对 Web 存储(存储少量数据)的一种补充,属于事务数据库系统。它的 API 使用索引来对这些数据进行高性能搜索,并且通过 API 可以方便保存和读取 JavaScript 对象。它的操作完全是异步进行的,通常要注册 onerror 或者 onsuccess 事件处理程序,监听操作的结果。


不同于 MySql 或 Oracle 这类用表来保存数据的数据库,IndexedDB 采用的是使用对象保存数据。因此 IndexedDB 可以理解为一组在相同命名空间下的对象的集合。

IndexedDB 相关 API
  1. 数据库:IDBDatabase 对象

数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。

  1. 对象仓库:IDBObjectStore 对象

每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格

  1. 索引:IDBIndex 对象

为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。

  1. 事务:IDBTransaction 对象

数据记录的读写和删改,都要通过事务完成。事务对象提供errorabortcomplete三个事件,用来监听操作结果。

  1. 操作请求:IDBRequest 对象

  2. 指针:IDBCursor 对象

  3. 主键集合:IDBKeyRange 对象

操作 IndexedDB 的基本模式
  1. 打开一个数据库

  2. 在数据库中创建对象存储

  3. 启动事务并请求执行一些数据库操作,例如添加或检索数据

  4. 过侦听事件来等待操作完成

  5. 对结果做一些事情(可以在 request object 上找到)

API 使用及流程详解
let req,db;let version = 2/*  * 【第一:创建数据库或升级数据库】 * 发送打开'demo'数据库的请求,若不存在先创建,再打开,返回一个IDBOpenDBRequest请求实例 * req解答: * 原型链: req.__proto__ = IDBOpenDBRequest;IDBOpenDBRequest.__proto__ = IDBRequest * IndexedDB中的大多数其他异步函数执行都是相同的操作-返回带有onerror或onsuccess的IDBRequest对象 *  * version解答: * 数据库的版本,确定数据库模式-数据库中存储的对象及其结构. */ req = indexedDB.open('demo',version)req.onerror = function(e){	console.log('发生错误',e.target.errorCode)}// 成功req.onsuccess = function(e){  // 返回的是IDBDatabase数据库实例	db = e.target.result  /*    * 处理错误事件:   * 错误事件是冒泡的。错误事件以生成错误的请求为目标,然后事件冒泡到事务中,最后到数据库对象。   * 如果要避免向每个请求添加错误处理程序,则可以在数据库对象上添加单个错误处理程序。   */   db.onerror = function(event) {  	console.error("Database error: " + event.target.errorCode);	}}
/* * 【第二:监听数据库升级】 * 创建或更新数据库的版本将触发此事件 * 是唯一可以更改数据库结构的地方。在其中可以创建和删除对象存储以及构建和删除索引。 */ req.onupgradeneeded = function(e){ db = e.target.result /* * 【第三:创建表】 * 为此数据库创建一个objectStore createObjectStore(存储名,参数对象) * 可以理解为:创建一张user的表,主键是id,若无合适主键,可配置自增主键{autoIncrement: true */ var objectStore if(!db.objectStoreNames.contains('user')){ // 不存在user表,则创建user表,主键为id objectStore =db.createObjectStore("user", { keyPath: "id" }); } /* * 【第四:创建索引】 * (索引名称,索引所在的属性,索引配置对象) */ objectStore.createIndex("id", "id", { unique: true }); /* * 【第五:写入数据】 * 通过IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 对象, * 再通过表格对象的add()方法,向表格写入一条记录 * 写入过程是异步的,通过监听success/error获取写入结果 */ var addUserReq = db.transaction(['user'],'readwrite') .objectStore('user').add({id:1,name:'张三',age:18}) addUserReq.onsuccess = function(){console.log('user表写入数据成功')} addUserReq.onerror = function(){console.log('user表写入数据失败')} /* * 【第六:读取数据】 * 读取数据也是通过事务完成 */ var readTransaction = db.transaction(['user']) var readUserObjStore = readTransaction.objectStore('user'); // 读取主键为1的数据 var readReq = readUserObjStore.get(1) readReq.onerror = function(){console.log('读取数据失败')} readReq.onsuccess = function(event){ if (request.result) { console.log('Name: ' + request.result.name); console.log('Age: ' + request.result.age); } else { console.log('未获得数据记录'); } } /* * 【第七:更新数据】 */ var putUserReq = db.transaction(['user'],'readwrite') .objectStore('user').put({id:1,name:'张三',age:20}) putUserReq.onsuccess = function(){console.log('user表更新数据成功')} putUserReq.onerror = function(){console.log('user表更新数据失败')}}
/* * 【第八:删除数据】 */ var deleteUserReq = db.transaction(['user'],'readwrite') .objectStore('user').delete(1) deleteUserReq.onsuccess = function(){console.log('user表删除数据成功')}
/* * 【第九:遍历表格数据】 * 使用针对对象IDBCursor */ var cursorStore = db.transaction('user').objectStore('user') cursorStore.openCursor().onsuccess = function(event){ var cursor = event.target.result if(cursor){ console.log('id:'+cursor.key) console.log('name:'+cursor.value.name) console.log('age:'+cursor.value.age) cursor.continue() }else{console.log('无更多数据')} } /* * 【第十:自定义索引】 */ objectStore.createIndex('name', 'name', { unique: false }); var transaction = db.transaction(['user'], 'readonly'); var store = transaction.objectStore('user'); var index = store.index('name'); var request = index.get('张三'); request.onsuccess = function (e) { var result = e.target.result; console.log('result',result) }
复制代码


简易封装 IndexedDB
import isString from 'lodash/isString'
/** * @param dbName 数据库名称 * @param version 数据库版本 不传默认为1 * @param primary 数据库表主键 * @param indexList Array 数据库表的字段以及字段的配置,每项为Object,结构为{ name, keyPath, options } */class WebDB { constructor({dbName, version, primary, indexList}) { this.db = null this.objectStore = null this.request = null this.primary = primary this.indexList = indexList this.version = version this.intVersion = parseInt(version.replace(/\./g, '')) this.dbName = dbName try { this.open(dbName, this.intVersion) } catch (e) { throw e } }
open (dbName, version) { const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB; if (!indexedDB) { console.error('你的浏览器不支持IndexedDB') } this.request = indexedDB.open(dbName, version) this.request.onsuccess = this.openSuccess.bind(this) this.request.onerror = this.openError.bind(this) this.request.onupgradeneeded = this.onupgradeneeded.bind(this) }
onupgradeneeded (event) { console.log('onupgradeneeded success!') this.db = event.target.result const names = this.db.objectStoreNames if (names.length) { for (let i = 0; i< names.length; i++) { if (this.compareVersion(this.version, names[i]) !== 0) { this.db.deleteObjectStore(names[i]) } } } if (!names.contains(this.version)) { this.objectStore = this.db.createObjectStore(this.version, { keyPath: this.primary }) this.indexList.forEach(index => { const { name, keyPath, options } = index this.objectStore.createIndex(name, keyPath, options) }) } }
openSuccess (event) { console.log('openSuccess success!') this.db = event.target.result }
openError (event) { console.error('数据库打开报错', event) // 重新链接数据库 if (event.type === 'error' && event.target.error.name === 'VersionError') { indexedDB.deleteDatabase(this.dbName); this.open(this.dbName, this.intVersion) } }
compareVersion (v1, v2) { if (!v1 || !v2 || !isString(v1) || !isString(v2)) { throw '版本参数错误' } const v1Arr = v1.split('.') const v2Arr = v2.split('.') if (v1 === v2) { return 0 } if (v1Arr.length === v2Arr.length) { for (let i = 0; i< v1Arr.length; i++) { if (+v1Arr[i] > +v2Arr[i]) { return 1 } else if (+v1Arr[i] === +v2Arr[i]) { continue } else { return -1 } } } throw '版本参数错误' }
/** * 添加记录 * @param record 结构与indexList 定下的index字段相呼应 * @return Promise */ add (record) { if (!record.key) throw '需要添加的key为必传字段!' return new Promise((resolve, reject) => { let request try { request = this.db.transaction([this.version], 'readwrite').objectStore(this.version).add(record) request.onsuccess = function (event) { resolve(event) }
request.onerror = function (event) { console.error(`${record.key},数据写入失败`) reject(event) } } catch (e) { reject(e) } }) }
/** * 读取记录 * @param key 主键的值 * @return Promise */ get (key) { return new Promise((resolve, reject) => { setTimeout(() => { let request try { request = this.db.transaction([this.version]).objectStore(this.version).get(key) request.onerror = function (event) { console.error(`${key}, 数据读取失败!`) reject(event) }
request.onsuccess = function (event) { if (request.result) { resolve(request.result) } else { reject(event) } } } catch (e) { reject(e) } }, 200) }) }
/** * 更新记录 * @param record 结构与indexList 定下的index字段相呼应 * @return Promise */ update (record) { return new Promise((resolve, reject) => { // const request = this.db.transaction([this.version], 'readwrite').objectStore(this.version).put(record) let request try { request = this.db.transaction([this.version], 'readwrite').objectStore(this.version).put(record) request.onsuccess = function (event) { resolve(event) };
request.onerror = function (event) { console.error(`${record.key},数据更新失败`) reject(event) } } catch (e) { reject(e) } }) }
/** * 删除记录 * @param key 主键的值 * @return Promise */ remove (key) { return new Promise((resolve, reject) => { const request = this.db.transaction([this.version], 'readwrite').objectStore(this.version).delete(key);
request.onsuccess = function (event) { resolve(event) } request.onerror = function (event) { console.error(`${key}, 数据删除失败`) reject(event) } }) }}
export default WebDB
复制代码
总结

至此我们学习的 IndexedDB 数据库的使用,关于使用场景,可以在做离线缓存等场景中使用,而我当时在做 chrome 扩展程序热更新方案时候使用到,用于缓存热更新业务代码到本地提升资源效率。


最后附上:Chrome 扩展程序文章

解决大中型浏览器 (Chrome) 插件开发痛点:自定义热更新方案——1. 原理分析及构建部署实现

解决大中型浏览器 (Chrome) 插件开发痛点:自定义热更新方案——2. 基于双缓存更新功能模块


发布于: 2021 年 01 月 22 日阅读数: 314
用户头像

梁龙先森

关注

脚踏V8引擎的无情写作机器 2018.03.17 加入

还未添加个人简介

评论

发布
暂无评论
重学JS | 一文看懂浏览器数据库IndexedDB详细操作