indexDB的用法示例

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>IndexedDB 简单示例</title>
  <style>
    body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
    input, button { margin: 4px; padding: 6px 12px; }
    #output { white-space: pre-wrap; background: #f5f5f5; padding: 12px; margin-top: 16px; border-radius: 4px; }
  </style>
</head>
<body>
  <h2>IndexedDB 简单示例</h2>

  <div>
    <input id="keyInput" placeholder="键 (key)" />
    <input id="valueInput" placeholder="值 (value)" />
  </div>
  <div style="margin-top: 8px;">
    <button onclick="setItem()">存数据</button>
    <button onclick="getItem()">取数据</button>
    <button onclick="removeItem()">删数据</button>
    <button onclick="getAllData()">查询全部</button>
  </div>
  <div id="output">结果将显示在这里...</div>

  <script>
    /**
     * 根据数据库名称,存放打开数据库的请求,一个数据库始终保持只有一个连接
     * - 方便获取数据库的连接
     * - 关闭指定数据库的连接
     */
    const dbRequestsDic = {}

    /**
     * 根据数据库名和表名获取数据库连接,没有则创建
     * - 调用 createObjectStore 来创建表,
     *   创建表必须在数据库请求的 onupgradeneeded 回调中执行,
     *   onupgradeneeded 回调的触发条件是数据库创建或升级,
     *   而数据库升级前还必须关闭该数据库的所有连接
     * @param DBName 数据库名称
     * @param tableName 表名
     * @returns Promise<IDBDatabase>
     */
    function getDatabase(DBName, tableName) {
      return new Promise((resolve, reject) => {
        // 复用已有的打开请求,保证同一个数据库只有一个连接
        dbRequestsDic[DBName] = dbRequestsDic[DBName] || indexedDB.open(DBName)
        const request = dbRequestsDic[DBName]

        if (request.readyState === 'done') {
          // 请求已完成,可以直接拿到数据库连接
          const dataBase = request.result // 表示一个数据库连接
          if (dataBase.objectStoreNames.contains(tableName)) {
            // 表已存在,直接返回数据库连接
            resolve(request.result)
          } else {
            // 表不存在,需要关闭连接 → 升级版本 → 重新打开 → 建表
            const newVersion = dataBase.version + 1 // 版本号 +1,确保触发升级
            dataBase.close() // 升级前必须关闭当前连接
            const newRequest = indexedDB.open(DBName, newVersion) // 用新版本号重新打开
            // upgradeneeded:数据库版本变更时触发,这是唯一能建表的时机
            newRequest.addEventListener('upgradeneeded', () => {
              newRequest.result.createObjectStore(tableName, { keyPath: 'id' }) // 新增表,主键为 id
            })
            dbRequestsDic[DBName] = newRequest // 更新字典,保存新请求
            newRequest.addEventListener('success', () => resolve(newRequest.result))
            newRequest.addEventListener('error', reject)
          }
        } else if (request.readyState === 'pending') {
          // 请求还在进行中(比如第一次调用,数据库正在创建/打开)
          // upgradeneeded:数据库首次创建或版本升级时触发
          request.addEventListener('upgradeneeded', () => {
            if (!request.result.objectStoreNames.contains(tableName)) {
              request.result.createObjectStore(tableName, { keyPath: 'id' }) // 表不存在则建表
            }
          })
          request.addEventListener('success', () => resolve(request.result))
          request.addEventListener('error', reject)
        }
      })
    }

    class DBCache {
      /**
       * IndexedDB 构造函数
       * @param DBName 数据库名称
       * @param storageName 仓库名称(表名)
       */
      constructor(DBName, storageName) {
        this.DBName = DBName
        this.storageName = storageName
      }

      /** 数据库名称 */
      DBName
      /** 仓库名称 */
      storageName

      /** 私有方法:获取数据库连接 */
      #getDB() {
        return getDatabase(this.DBName, this.storageName)
      }

      /**
       * 私有方法:内部查询,只返回 value 部分
       * 存储结构是 { id, value },对外只返回 value,id 对使用者透明
       */
      #_getItem(key) {
        return new Promise(async (resolve, reject) => {
          const db = await this.#getDB()
          const transaction = db.transaction([this.storageName]) // 只读事务
          const objectStore = transaction.objectStore(this.storageName) // 获取仓库对象
          const request = objectStore.get(key) // 按主键查询

          request.onerror = reject
          request.onsuccess = function () {
            // request.result 是 { id: key, value: data },只返回 value 部分
            resolve(request.result == undefined ? undefined : request.result.value)
          }
        })
      }

      /**
       * 存储数据
       * @param key 键
       * @param data 需要存储的数据,不能存储 Proxy 对象(如 Vue 3 的 reactive)
       * 先查询 key 是否存在:存在用 put 更新,不存在用 add 新增
       */
      setItem(key, data) {
        return new Promise(async (resolve, reject) => {
          const db = await this.#getDB()
          const oldData = await this.#_getItem(key) // 先查是否已存在

          const transaction = db.transaction([this.storageName], 'readwrite') // 读写事务
          const objectStore = transaction.objectStore(this.storageName)

          let request
          if (oldData) {
            request = objectStore.put({ id: key, value: data }) // 已存在 → put 更新
          } else {
            request = objectStore.add({ id: key, value: data }) // 不存在 → add 新增
          }

          request.onsuccess = resolve
          request.onerror = reject
        })
      }

      /**
       * 根据id获取数据
       * @param key 键
       * @returns value 部分,不存在则返回 undefined
       */
      getItem(key) {
        return this.#_getItem(key)
      }

      /**
       * 根据id删除数据
       * @param key 键
       */
      removeItem(key) {
        return new Promise(async (resolve, reject) => {
          const db = await this.#getDB()
          const transaction = db.transaction([this.storageName], 'readwrite') // 读写事务
          const objectStore = transaction.objectStore(this.storageName)
          const request = objectStore.delete(key) // 按主键删除

          request.onerror = reject
          request.onsuccess = resolve
        })
      }
    }

    // 创建实例
    const cache = new DBCache('myDatabase', 'myStore')

    // ─── 页面交互 ───

    async function setItem() {
      const key = document.getElementById('keyInput').value.trim()
      const value = document.getElementById('valueInput').value.trim()
      if (!key || !value) return show('请输入键和值')
      try {
        await cache.setItem(key, value)
        show(`存储成功: { id: "${key}", value: "${value}" }`)
      } catch (e) {
        show(`存储失败: ${e}`)
      }
    }

    async function getItem() {
      const key = document.getElementById('keyInput').value.trim()
      if (!key) return show('请输入键')
      try {
        const result = await cache.getItem(key)
        show(result !== undefined ? `查询结果: ${JSON.stringify(result)}` : `未找到 key="${key}"`)
      } catch (e) {
        show(`查询失败: ${e}`)
      }
    }

    async function removeItem() {
      const key = document.getElementById('keyInput').value.trim()
      if (!key) return show('请输入键')
      try {
        await cache.removeItem(key)
        show(`删除成功: key="${key}"`)
      } catch (e) {
        show(`删除失败: ${e}`)
      }
    }

    async function getAllData() {
      try {
        const db = await getDatabase('myDatabase', 'myStore')
        const request = db.transaction(['myStore'])
          .objectStore('myStore')
          .getAll()

        request.onsuccess = (e) => {
          const results = e.target.result
          show(results.length ? `全部数据:\n${JSON.stringify(results, null, 2)}` : '仓库为空')
        }
        request.onerror = (e) => show(`查询失败: ${e.target.error}`)
      } catch (e) {
        show(`查询失败: ${e}`)
      }
    }

    function show(msg) {
      document.getElementById('output').textContent = msg
    }
  </script>
</body>
</html>
相关推荐
牛奶1 小时前
开发者的"奇技淫巧":那些让你效率翻倍的实战技巧
前端·后端·程序员
咸鱼翻身更入味1 小时前
Vue创建一个简单的Agent聊天——工具调用
前端
walking9571 小时前
重新学习前端之设计模式与架构
前端·javascript·面试
walking9571 小时前
重新学习前端之TypeScript
前端·javascript·面试
walking9571 小时前
重新学习前端之Linux
前端·vue.js·面试
walking9571 小时前
重新学习前端之CSS
前端·vue.js·面试
walking9571 小时前
重新学习前端之Git
前端·vue.js·面试
walking9571 小时前
重新学习前端之小程序
前端
魔术师Grace1 小时前
AI让我退化成原始人了
前端·程序员