本文是很久之前发布在CSDN上的,现转载到掘金上。原文链接
〇、为什么使用IndexedDB
最近遇到一个需求,一个页面要上传至少五六十张图片,同时支持页面内回显,但是后台的储存服务是一个异步服务,图片上传以后大概10分钟后才能查回来,可页面又要上传后能够立即回显查看,那就只能存储在前端了,也就是说需要在一个页面中存储至少几十张图片。
众所周知,前端页面存储内容过多后可能直接导致页面崩溃,接下来想到了存在本地缓存中,但是常用的sessionStorage和localStorage一般只能存储5M的数据。
新的问题出现了,现在的手机拍的图片体积都比较大,尤其是转成base64后体积又会增长大约30%,即使采用压缩都压缩到100K,最多也就能存50张图片,况且也几乎不可能压缩到那么小,否则就成马赛克了,这肯定是无法满足几十张图片的存储了。
在查询资料的过程中发现了IndexedDB数据库,IndexedDB是一种浏览器数据库,储存空间大,一般不少于 250MB,甚至部分浏览器没有上限,以下是关于IndexedDB的一些介绍和我使用IndexedDB时的使用方法和一些坑。
一、IndexedDB的概念及特点
1.1 概念(详情参考MDN)
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。
1.2 特点(详情参考MDN)
-
键值对储存:
IndexedDB内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括JavaScript对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。 -
异步:
IndexedDB操作时不会锁死浏览器,用户依然可以进行其他操作,这与LocalStorage形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。 -
支持事务:
IndexedDB支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。 -
同源限制:
IndexedDB受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。 -
储存空间大:
IndexedDB的储存空间比LocalStorage大得多,一般来说不少于 250MB,甚至没有上限。 -
支持二进制储存:
IndexedDB不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer对象和Blob对象)。
二、基本使用
我是在vue项目中使用的,就直接封装了一个方法,包含初始化数据库、关闭数据库、新增数据、读取数据、清空数据库等几个方法。
2.1 封装indexDB.JS
2.1.1 声明indexDB
const indexDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB
2.1.2 创建构造函数
这里只针对当前这个需求,所以数据库名和表名都直接写死了。
class IndexDBCache {
// 构造函数
constructor() {
this._db = null //数据库
this._transaction = null //事务
this._request = null
this._dbName = 'test' //数据库名
this._cacheTableName = 'imageCache' //表名
this._dbversion = 1 //数据库版本
}
}
export default IndexDBCache
2.1.3 初始化数据库
主键名就是图片的名称,这里直接写死了,主键必须是唯一的不允许重复,也可以设置索引,数据量特别大时,效率会更高,不过我这里不需要,就没有设置索引。
// 初始化数据库
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)
}
})
}
2.1.4 关闭数据库
关闭数据库的方法,在数据库使用完后要关闭。
// 关闭数据库
closeDB() {
this._db.close()
console.log(`关闭数据库`)
}
2.1.5 新增数据的方法
注意:
indexedDB都是异步操作,具体操作可在回调函数中写。
/**
* @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)
}
})
}
2.1.6 读取数据的方法
这里读取数据就直接根据主键来获取数据库中的数据,如果初始化数据库时设置了索引,也可以根据索引来获取,数据量大时,效率应该更高,不过我这里只有几十个图片的数据,就无所谓了。
// 通过主键读取数据
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)
}
})
}
2.1.7 清空数据库数据
在不需要这些数据时,要把数据库的数据删除掉,否则会一直存储在浏览器中。
// 清空数据库数据
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.2 组件中使用
2.2.1 引入并初始化数据建库
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。
2.2.2 写入数据库
// 写入数据库
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则是存入的数据对象。
2.2.3 从数据库读取数据
//从数据库获取数据
getImageByName (imageName) {
imageDB.getDataByKey(imageName).then((res) =>{
console.log('从indexDB数据库获取数据成功', res)
}).catch((err) =>{
console.log('从indexDB数据库获取数据失败==>', err)
})
},
根据主键从数据库中获取数据,从控制台可以直接打印出取出的数据。
2.2.4 清空数据库的操作
// 清空数据库的数据
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()
}
执行结果,在执行删除操作后,可以看到控制台只打印了删除数据库和开始删除数据库,后面的删除数据库成功和删除数据库失败根本没有打印出来,查看数据库,发现数据库和表中的数据都在,从页面上获取数据,也能够成功获取到,证明数据库没有删除成功,为了在离开页面后清空数据,但又删不掉数据库,所以采取了离开当前页面清空数据库的方式来删除数据,如果您有更好的方法,欢迎一起探讨。   