持久化储存之IndexDB常用方法封装

这篇博客介绍了如何封装IndexedDB,提供了一套包括打开、关闭数据库,创建、删除仓库对象,创建、删除索引以及增删改查数据的同步和异步操作。通过Promise封装异步请求,简化了IndexedDB的复杂操作,便于在Web应用中存储大量结构化数据。

官方解释:IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。

IndexedDB提供了一套完整的API,包括打开数据库连接,创建数据库对象,增删改查等,然而这些API当中存在部分异步、部分同步的情况,异步接口并非Promise形式,操作起来稍有点复杂,相比localStorage来说,于是我对常用的一些方法进行了二次封装。

创建

创建一个数据库操作对象,并返回一个Object对象
数据库版本默认为: 1

/**
 * 创建一个数据库连接
 * @param {String} dbName 数据库名
 * @param {Number} version 数据库版本
 */
function createIndexDB(dbName, version = 1) {
  if (!dbName) {
    throw new Error('dbName is required.')
  }

  let db

  return {}
}

由于原生的IndexDB操作大部分是属于异步请求事件,则用Promise封装一个公共的方法

const createAsyncRequest = (request, options) => {
  return new Promise((resolve, reject) => {
    request.onsuccess = function (e) {
      res = e.target.result
      resolve(res)
    }

    request.onerror = function (e) {
      reject(new Error(e.target.error))
    }
  })
}

打开

打开数据库,不存在时会默认创建这个数据库,返回Promise

/**
 * 创建一个数据库连接
 * @param {String} dbName 数据库名
 * @param {Number} version 数据库版本
 */
function createIndexDB(dbName, version = 1) {
  if (!dbName) {
    throw new Error('dbName is required.')
  }

  let db

  return {
    open() {
      return new Promise((resolve, reject) => {
        const request = window.indexedDB.open(dbName, version)
        request.onsuccess = function (e) {
          db = e.target.result
          resolve(db)
        }

        request.onerror = function (e) {
          reject(new Error(e.target.error))
        }

        // 数据库创建或升级的时触发
        request.onupgradeneeded = function (e) {
          db = e.target.result
          resolve(db)
        }
      })
    },
  }
}

关闭

关闭数据库

function close() {
  const request = db.open(dbName)
  return createAsyncRequest(request)
}

创建仓库对象

创建仓库数据对象(表) [同步]

function createObjectStore(storeName, options = {}) {
  const _options = {
    keyPath: 'id',
    ...options
  }

  try {
    if (!db.objectStoreNames.contains(storeName)) {
      let objectStore = db.createObjectStore(storeName, _options)
      return objectStore
    } else {
      throw new Error(`The storeName <${storeName}> is exist.`)
    }
  } catch (e) {
    throw new Error(e.target.error)
  }
}

删除仓库对象

删除仓库数据对象(表) [同步]

function deleteObjectStore(storeName) {
  try {
    if (!db.objectStoreNames.contains(storeName)) {
      return db.deleteObjectStore(storeName)
    } else {
      throw new Error(`The storeName <${storeName}> is not exist.`)
    }
  } catch (e) {
    throw new Error(e.target.error)
  }
}

创建索引

基于仓库数据对象创建索引

// 创建索引 [同步]
function createIndex(objectStore, options = {}) {
  const {indexName, keyPath, objectParameters} = options

  try {
    if (objectStore) {
      return objectStore.createIndex(indexName, keyPath, objectParameters) 
    } else {
      throw new Error(`The objectStore is required.`)
    }
  } catch (e) {
    throw new Error(e.target.error)
  }
}

删除索引

基于仓库数据对象删除索引

// 删除索引 [同步]
function deleteIndex(objectStore, indexName) {
  try {
    if (objectStore) {
      return objectStore.deleteIndex(indexName) 
    } else {
      throw new Error(`The objectStore is required.`)
    }
  } catch (e) {
    throw new Error(e.target.error)
  }
}

操作数据对象

由于增删改查数据都需要操作仓库数据对象,则可将其封装成公共方法

function transactionObjectStore(storeName, options) {
  const {model = 'readwrite', oncomplete, onerror} = options

  try {
    const transaction = db.transaction([storeName], model)
    transaction.oncomplete = oncomplete
    transaction.onerror = function(e) {
      onerror && onerror(e)
      throw new Error(e.target.error)
    }

    const objectStore = transaction.objectStore(storeName)
    return objectStore
  } catch (e) {
    throw new Error(e.target.error)
  }
}

基于仓库数据对象插入新数据

function add(storeName, data) {
  let request = transactionObjectStore(storeName).add(data)
  return createAsyncRequest(request)
}

基于仓库数据对象删除数据

function delete(storeName, key) {
  let request = transactionObjectStore(storeName).delete(key)
  return createAsyncRequest(request)
}

基于仓库数据对象更新数据

function put(storeName, data) {
  let request = transactionObjectStore(storeName).put(data)
  return createAsyncRequest(request)
}

基于仓库数据对象查询数据

function get(storeName, key) {
  let request = transactionObjectStore(storeName, {model: 'readonly'}).get(key)
  return createAsyncRequest(request)
}

查-所有

基于仓库数据对象查询所有数据

function getAll(storeName, query, count) {
  let request = transactionObjectStore(storeName).getAll(query, count)
  return createAsyncRequest(request)
}

查-条目数

基于仓库数据对象查询条目数

function count(storeName, query) {
  let request = transactionObjectStore(storeName).count(query)
  return createAsyncRequest(request)
}

代码清单

// util.indexDB.js

/**
 * 创建一个数据库连接
 * @param {Object} options 选项
 */
function createIndexDB(dbName, version = 1) {
  if (!dbName) {
    throw new Error('dbName is required.')
  }

  let db
  const createAsyncRequest = (request, options) => {
    return new Promise((resolve, reject) => {
      request.onsuccess = function (e) {
        res = e.target.result
        resolve(res)
      }

      request.onerror = function (e) {
        reject(new Error(e.target.error))
      }
    })
  }

  return {
    // 打开数据库,不存在时会默认创建
    open() {
      return new Promise((resolve, reject) => {
        const request = window.indexedDB.open(dbName, version)
        request.onsuccess = function (e) {
          db = e.target.result
          resolve(db)
        }

        request.onerror = function (e) {
          reject(new Error(e.target.error))
        }

        // 数据库创建或升级的时触发
        request.onupgradeneeded = function (e) {
          db = e.target.result
          resolve(db)
        }
      })
    },

    // 关闭数据库
    close() {
      const request = db.open(dbName)
      return createAsyncRequest(request)
    },

    // 创建仓库对象(表) [同步]
    createObjectStore(storeName, options = {}) {
      const _options = {
        keyPath: 'id',
        ...options
      }

      try {
        if (!db.objectStoreNames.contains(storeName)) {
          let objectStore = db.createObjectStore(storeName, _options)
          return objectStore
        } else {
          throw new Error(`The storeName <${storeName}> is exist.`)
        }
      } catch (e) {
        throw new Error(e.target.error)
      }
    },

    // 删除仓库对象(表) [同步]
    deleteObjectStore(storeName) {
      try {
        if (!db.objectStoreNames.contains(storeName)) {
          return db.deleteObjectStore(storeName)
        } else {
          throw new Error(`The storeName <${storeName}> is not exist.`)
        }
      } catch (e) {
        throw new Error(e.target.error)
      }
    },

    // 创建索引 [同步]
    createIndex(objectStore, options = {}) {
      const {indexName, keyPath, objectParameters} = options

      try {
        if (objectStore) {
          return objectStore.createIndex(indexName, keyPath, objectParameters) 
        } else {
          throw new Error(`The objectStore is required.`)
        }
      } catch (e) {
        throw new Error(e.target.error)
      }
    },

    // 删除索引 [同步]
    deleteIndex(objectStore, indexName) {
      try {
        if (objectStore) {
          return objectStore.deleteIndex(indexName) 
        } else {
          throw new Error(`The objectStore is required.`)
        }
      } catch (e) {
        throw new Error(e.target.error)
      }
    },

    // 操作数据对象
    transactionObjectStore(storeName, options) {
      const {model = 'readwrite', oncomplete, onerror} = options

      try {
        const transaction = db.transaction([storeName], model)
        transaction.oncomplete = oncomplete
        transaction.onerror = function(e) {
          onerror && onerror(e)
          throw new Error(e.target.error)
        }

        const objectStore = transaction.objectStore(storeName)
        return objectStore
      } catch (e) {
        throw new Error(e.target.error)
      }
    },

    // 插入数据
    add(storeName, data) {
      let request = transactionObjectStore(storeName).add(data)
      return createAsyncRequest(request)
    },

    // 查询数据
    get(storeName, key) {
      let request = transactionObjectStore(storeName, {model: 'readonly'}).get(key)
      return createAsyncRequest(request)
    },

    // 查询数据
    getAll(storeName, query, count) {
      let request = transactionObjectStore(storeName).getAll(query, count)
      return createAsyncRequest(request)
    },

    // 更新数据
    put(storeName, data) {
      let request = transactionObjectStore(storeName).put(data)
      return createAsyncRequest(request)
    },

    // 删除数据
    delete(storeName, key) {
      let request = transactionObjectStore(storeName).delete(key)
      return createAsyncRequest(request)
    },

    // 清除数据
    clear(storeName) {
      let request = transactionObjectStore(storeName).clear()
      return createAsyncRequest(request)
    },

    // 条目数
    count(storeName, query) {
      let request = transactionObjectStore(storeName).count(query)
      return createAsyncRequest(request)
    },
  }
}
### 封装 IndexedDB 数据存储方法 IndexedDB 是一种强大的客户端存储解决方案,能够处理大量的结构化数据以及二进制数据。为了简化其复杂的操作流程,可以通过封装一个通用的 API 来管理 IndexedDB 的操作[^2]。 以下是基于 Vue2 的封装示例代码,涵盖了创建数据库、打开事务、增删改查等功能: #### 1. 创建 IndexedDB 工具类 ```javascript class IndexedDBManager { constructor(dbName, dbVersion, storeNames) { this.dbName = dbName; this.dbVersion = dbVersion; this.storeNames = storeNames; // 数组形式传入需要创建的对象仓库名称 this.db = null; this.openDatabase(); } openDatabase() { const request = indexedDB.open(this.dbName, this.dbVersion); request.onerror = (event) => { console.error('数据库打开失败', event); }; request.onsuccess = (event) => { this.db = event.target.result; console.log('数据库已成功打开'); }; request.onupgradeneeded = (event) => { this.db = event.target.result; this.storeNames.forEach((storeName) => { if (!this.db.objectStoreNames.contains(storeName)) { this.db.createObjectStore(storeName, { keyPath: 'id', autoIncrement: true }); } }); console.log('数据库升级完成'); }; } addData(storeName, data) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const objectStore = transaction.objectStore(storeName); const request = objectStore.add(data); request.onsuccess = () => { resolve(request.result); }; request.onerror = () => { reject(request.error); }; }); } getData(storeName, id) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readonly'); const objectStore = transaction.objectStore(storeName); const request = objectStore.get(id); request.onsuccess = () => { resolve(request.result); }; request.onerror = () => { reject(request.error); }; }); } updateData(storeName, data) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const objectStore = transaction.objectStore(storeName); const request = objectStore.put(data); request.onsuccess = () => { resolve(request.result); }; request.onerror = () => { reject(request.error); }; }); } deleteData(storeName, id) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const objectStore = transaction.objectStore(storeName); const request = objectStore.delete(id); request.onsuccess = () => { resolve(); }; request.onerror = () => { reject(request.error); }; }); } clearStore(storeName) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const objectStore = transaction.objectStore(storeName); const request = objectStore.clear(); request.onsuccess = () => { resolve(); }; request.onerror = () => { reject(request.error); }; }); } } ``` #### 2. 使用工具类进行数据操作 以下是一个简单的 Vue2 组件示例,展示如何使用上述 `IndexedDBManager` 类来进行数据存取操作: ```html <template> <div> <h3>IndexedDB 示例</h3> <button @click="addRecord">新增记录</button> <button @click="fetchRecords">获取记录</button> <ul> <li v-for="(record, index) in records" :key="index">{{ record.name }} - {{ record.age }}</li> </ul> </div> </template> <script> export default { data() { return { records: [], dbManager: null, }; }, created() { this.initDB(); }, methods: { async initDB() { this.dbManager = new IndexedDBManager('TestDB', 1, ['users']); }, async addRecord() { try { await this.dbManager.addData('users', { name: 'Alice', age: 25 }); console.log('记录已添加'); } catch (error) { console.error('添加记录失败:', error); } }, async fetchRecords() { try { let allRecords = []; const transaction = this.dbManager.db.transaction(['users'], 'readonly'); const objectStore = transaction.objectStore('users'); objectStore.openCursor().onsuccess = (event) => { const cursor = event.target.result; if (cursor) { allRecords.push(cursor.value); cursor.continue(); } else { this.records = allRecords; } }; } catch (error) { console.error('获取记录失败:', error); } }, }, }; </script> ``` #### 需要注意的事项 - **兼容性**:尽管现代浏览器普遍支持 IndexedDB,但在某些老旧版本中可能存在不兼容的情况。建议在实际应用前检测环境的支持情况。 - **错误处理**:IndexedDB 操作可能会因多种原因失败(如配额不足),因此应始终捕获并妥善处理这些异常。 - **性能优化**:对于大规模的数据读写操作,考虑分批执行以减少阻塞时间[^1]。 #### 常见问题 - 如果尝试访问不存在的对象仓库,则会抛出错误。需确保对象仓库已被正确初始化。 - 当数据库模式发生变化时,必须通过 onupgradeneeded 方法更新现有架构。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值