【JavaScript】前端存储 —— IndexedDB

652 阅读7分钟

本文是很久之前发布在CSDN上的,现转载到掘金上。原文链接

〇、为什么使用IndexedDB

最近遇到一个需求,一个页面要上传至少五六十张图片,同时支持页面内回显,但是后台的储存服务是一个异步服务,图片上传以后大概10分钟后才能查回来,可页面又要上传后能够立即回显查看,那就只能存储在前端了,也就是说需要在一个页面中存储至少几十张图片。

众所周知,前端页面存储内容过多后可能直接导致页面崩溃,接下来想到了存在本地缓存中,但是常用的sessionStoragelocalStorage一般只能存储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就是设置的imageNamevalue则是存入的数据对象。

在这里插入图片描述

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()
   }

在离开页面时触发清空数据库数据的方法,清空数据库后可以看到表中的数据已经没有了。 ![在这里插入图片描述](i-blog.csdnimg.cn/blog_migrat… =800x)

三、踩到的坑

无法删除数据库

在上面的使用方法中可以看到,最后离开页面时使用了清空数据库的方法,为什么不直接删除掉数据库呢?因为我删不掉,下面是我使用的方法

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()
   }

执行结果,在执行删除操作后,可以看到控制台只打印了删除数据库开始删除数据库,后面的删除数据库成功删除数据库失败根本没有打印出来,查看数据库,发现数据库和表中的数据都在,从页面上获取数据,也能够成功获取到,证明数据库没有删除成功,为了在离开页面后清空数据,但又删不掉数据库,所以采取了离开当前页面清空数据库的方式来删除数据,如果您有更好的方法,欢迎一起探讨。 ![在这里插入图片描述](i-blog.csdnimg.cn/blog_migrat… =800x) ![在这里插入图片描述](i-blog.csdnimg.cn/blog_migrat… =800x) ![在这里插入图片描述](i-blog.csdnimg.cn/blog_migrat… =800x)