〇、为什么使用IndexedDB
最近遇到一个需求,一个页面要上传至少五六十张图片,同时支持页面内回显,但是后台的图片服务是一个异步的过程,图片上传以后可能10分钟后才能查回来,可页面又要上传后能够立即回显查看,那就只能存储在前端了,也就是说需要在一个页面中存储至少几十张图片。
众但前端页面存储内容过多后可能直接导致页面崩溃,接下来想到了存在本地缓存中,但是常用的sessionStorage
和localStorage
一般只能存储5M的数据,这就又有一个问题,现在的手机拍的图片体积都比较大,尤其是转成base64后体积又会增长大约30%,即使采用压缩都压缩到100K,最多也就能存50张图片,况且也不可能压缩到那么小,否则就成马赛克了,这肯定是无法满足几十张图片的存储了。
在查询资料的过程中发现了IndexedDB
数据库,IndexedDB
是一种浏览器数据库,储存空间大,一般不少于 250MB,甚至部分浏览器没有上限,以下是关于IndexedDB
的一些介绍和我使用IndexedDB
时的使用方法和一些坑。
一、IndexedDB的概念及特点
1、概念(详情参考MDN)
- IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。
2、特点(详情参考MDN)
- 键值对储存: IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 异步: IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- 支持事务: IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 同源限制: IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
- 储存空间大: IndexedDB 的储存空间比
LocalStorage
大得多,一般来说不少于 250MB,甚至没有上限。 - 支持二进制储存: IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
二、基本使用
我是在vue项目中使用的,就直接封装了一个方法,包含初始化数据库、关闭数据库、新增数据、读取数据、清空数据库等几个方法。
1、封装indexDB.JS
-
声明
indexDB
const indexDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB
-
创建构造函数
class IndexDBCache { // 构造函数 constructor() { this._db = null //数据库 this._transaction = null //事务 this._request = null this._dbName = 'test' //数据库名 this._cacheTableName = 'imageCache' //表名 this._dbversion = 1 //数据库版本 } } export default IndexDBCache
这里只针对当前这个需求,所以数据库名和表名都直接写死了。
-
初始化数据库
// 初始化数据库 initDB() { return new Promise((resolve, reject) => { this._request = indexDB.open(this._dbName, this._dbversion) // 打开数据库 // 数据库初始化成功 this._request.onsuccess = (event) => { this._db = this._request.result resolve(event) } // 数据库初始化失败 this._request.onerror = (event) => { reject(event) } // 数据库初次创建或更新时会触发 this._request.onupgradeneeded = (event) => { let db = this._request.result if (!db.objectStoreNames.contains(this._cacheTableName)) { db.createObjectStore(this._cacheTableName, { keyPath: 'imageName', // 设置主键 }) } resolve(event) } }) }
主键名就是图片的名称,这里直接写死了,主键必须是唯一的不允许重复,也可以设置索引,数据量特别大时,效率会更高,不过我这里不需要,就没有设置索引。
-
关闭数据库
// 关闭数据库 closeDB() { this._db.close() console.log(`关闭数据库`) }
关闭数据库的方法,在数据库使用完后要关闭。
-
新增数据的方法
/** * @description : 新增数据 * @param {Object} params 添加到数据库中的数据 { imageName: 文件名, image: base64格式图片 } * @return {*} */ addData(params) { return new Promise((resolve, reject) => { let transaction = this._db.transaction(this._cacheTableName, 'readwrite') let store = transaction.objectStore(this._cacheTableName) let response = store.add(params) // 操作成功 response.onsuccess = (event) => { resolve(event) } // 操作失败 response.onerror = (event) => { reject(event) } }) }
注意:indexedDB都是异步操作,具体操作可在回调函数中写
-
读取数据的方法
// 通过主键读取数据 getDataByKey(key) { return new Promise((resolve, reject) => { let transaction = this._db.transaction(this._cacheTableName) let objectStore = transaction.objectStore(this._cacheTableName) // 通过主键读取数据 let request = objectStore.get(key) // 操作成功 request.onsuccess = () => { resolve(request.result) } // 操作失败 request.onerror = (event) => { reject(event) } }) }
我这里读取数据就直接根据主键来获取数据库中的数据,如果初始化数据库时设置了索引,也可以根据索引来获取,数据量大时,效率应该更高,不过我这里只有几十个图片的数据,就无所谓了。
-
清空数据库数据
// 清空数据库数据 clearDB() { return new Promise((resolve, reject) => { let transaction = this._db.transaction(this._cacheTableName, 'readwrite') let store = transaction.objectStore(this._cacheTableName) let response = store.clear() // 操作成功 response.onsuccss = (event) => { resolve(event) } // 操作失败 response.onerror = (event) => { reject(event) } }) }
在不需要这些数据时,要把数据库的数据删除掉,否则会一直存储在浏览器中。
2、组件中使用
-
引入并初始化数据建库
import IndexDBCache from '../../utils/indexDB' let imageDB = null //初始化数据库 initIndexDB () { imageDB = new IndexDBCache() imageDB.initDB().then(res => { if (res.type == 'upgradeneeded') { console.log('indexDB 数据库创建或更新成功!') } else { console.log('indexDB 数据库初始化成功!') } }).catch((err) => { console.log('indexDB 数据库初始化失败! ', err) }) }
执行完数据库的初始化后,在浏览器的调试工具中可以看到新建的名为
test
的数据库和表名为imageCache
的数据表,同时可以看到表结构为key:value
的形式,主键为imageName
。
-
写入数据库
// 写入数据库 setData (imageName, imageData) { let data = { imageName, imageData } imageDB.addData(data).then((res) =>{ console.log('写入 indexDB 数据库成功', res) }).catch((err) =>{ console.log('写入 indexDB 数据库失败==>', err) }) },
写入数据库数据后,可以刷新后直接查看刚写入的数据,可以看到
key
就是设置的imageName
,value
则是存入的数据对象。
-
从数据库读取数据
//从数据库获取数据 getImageByName (imageName) { imageDB.getDataByKey(imageName).then((res) =>{ console.log('从indexDB数据库获取数据成功', res) }).catch((err) =>{ console.log('从indexDB数据库获取数据失败==>', err) }) },
根据主键从数据库中获取数据,从控制台可以直接打印出取出的数据。
-
清空数据库的操作
// 清空数据库的数据 clearIndexDB () { imageDB.clearDB() }
在离开页面时触发清空数据库数据的方法,清空数据库后可以看到表中的数据已经没有了,
三、踩到的坑
-
无法删除数据库
在上面的使用方法中可以看到,我最后离开页面时使用了清空数据库的方法,为什么不直接删除掉数据库呢?因为我删不掉,下面是我使用的方法
indexDB.js
中封装的删除数据库的方法// 删除数据库 deleteDB() { console.log('开始删除数据库') let DBDeleteRequest = indexedDB.deleteDatabase(this._dbName) DBDeleteRequest.onsuccess = function (event) { console.log('删除数据库成功') } DBDeleteRequest.onerror = function (event) { console.log('删除数据库失败') } }
组件中调用
// 删除数据库 delIndexDB () { console.log('删除数据库') imageDB.deleteDB() }
执行结果,在执行删除操作后,可以看到控制台只打印了
删除数据库
和开始删除数据库
,后面的删除数据库成功
和删除数据库失败
根本没有打印出来,查看数据库,发现数据库和表中的数据都在,从页面上获取数据,也能够成功获取到,证明数据库没有删除成功,为了在离开页面后清空数据,但又删不掉数据库,所以采取了离开当前页面清空数据库的方式来删除数据,如果您有更好的方法,欢迎一起探讨。