HTML5 IndexedDB

0 阅读4分钟

JavaScript 中的 IndexedDB,是 HTML5 提供的客户端非关系型(NoSQL)数据库,相比 Cookie、Web Storage 具备更大的存储容量、更强大的数据操作能力,是前端处理大量结构化数据的核心方案。

一、IndexedDB 核心概念(先理清术语)

IndexedDB 是异步操作的本地数据库,核心术语理解起来不难:

术语含义
数据库(DB)一个域名下可创建多个数据库,每个数据库有唯一名称
对象仓库(Store)类似关系型数据库的「表」,用于存储同类型数据(如 usergoods
索引(Index)为对象仓库的指定字段创建索引,加速数据查询(类似数据库索引)
事务(Transaction)操作数据库的最小单元,所有读写操作必须在事务中执行,保证操作原子性
游标(Cursor)遍历对象仓库中数据的工具,支持分页、条件筛选

二、完整使用示例(封装易用的工具函数)

IndexedDB 原生 API 偏底层且异步,我会封装一个通用工具类,包含「打开数据库、增删改查」核心操作,你可以直接复用:

1. 完整封装代码

运行

class IndexedDBHelper {
  constructor(dbName, version, storeConfig) {
    this.dbName = dbName; // 数据库名
    this.version = version; // 数据库版本(升级需递增)
    this.storeConfig = storeConfig; // 对象仓库配置 {name: 'user', keyPath: 'id'}
    this.db = null; // 数据库实例
  }

  // 1. 打开/创建数据库
  openDB() {
    return new Promise((resolve, reject) => {
      // 关闭已存在的连接
      if (this.db) this.db.close();

      // 打开数据库(不存在则创建)
      const request = indexedDB.open(this.dbName, this.version);

      // 数据库首次创建/版本升级时触发(用于初始化对象仓库/索引)
      request.onupgradeneeded = (e) => {
        this.db = e.target.result;
        const { name, keyPath, indexs } = this.storeConfig;

        // 判断对象仓库是否存在,不存在则创建
        if (!this.db.objectStoreNames.contains(name)) {
          const store = this.db.createObjectStore(name, { keyPath }); // keyPath 为主键

          // 可选:创建索引(如为name字段创建索引,允许重复)
          if (indexs && indexs.length) {
            indexs.forEach(({ field, unique }) => {
              store.createIndex(field, field, { unique });
            });
          }
        }
      };

      // 打开成功
      request.onsuccess = (e) => {
        this.db = e.target.result;
        resolve(this.db);
      };

      // 打开失败
      request.onerror = (e) => {
        reject(`打开数据库失败:${e.target.error.message}`);
      };
    });
  }

  // 2. 增/改数据(主键存在则更新,不存在则新增)
  putData(data) {
    return new Promise((resolve, reject) => {
      if (!this.db) return reject('数据库未打开');

      // 创建事务(读写模式),指定操作的对象仓库
      const transaction = this.db.transaction([this.storeConfig.name], 'readwrite');
      const store = transaction.objectStore(this.storeConfig.name);

      // 执行增/改操作
      const request = store.put(data); // 单条数据
      // 批量操作:store.put(data1); store.put(data2);

      request.onsuccess = () => resolve('操作成功');
      request.onerror = (e) => reject(`操作失败:${e.target.error.message}`);
    });
  }

  // 3. 根据主键查询单条数据
  getDataByKey(key) {
    return new Promise((resolve, reject) => {
      if (!this.db) return reject('数据库未打开');

      const transaction = this.db.transaction([this.storeConfig.name], 'readonly');
      const store = transaction.objectStore(this.storeConfig.name);

      const request = store.get(key);
      request.onsuccess = (e) => resolve(e.target.result);
      request.onerror = (e) => reject(`查询失败:${e.target.error.message}`);
    });
  }

  // 4. 查询所有数据(或通过游标筛选)
  getAllData(filterFn) {
    return new Promise((resolve, reject) => {
      if (!this.db) return reject('数据库未打开');

      const transaction = this.db.transaction([this.storeConfig.name], 'readonly');
      const store = transaction.objectStore(this.storeConfig.name);
      const result = [];

      // 打开游标遍历所有数据
      const request = store.openCursor();
      request.onsuccess = (e) => {
        const cursor = e.target.result;
        if (cursor) {
          // 可选:自定义筛选条件
          if (!filterFn || filterFn(cursor.value)) {
            result.push(cursor.value);
          }
          cursor.continue(); // 继续下一条
        } else {
          resolve(result); // 遍历完成
        }
      };
      request.onerror = (e) => reject(`查询失败:${e.target.error.message}`);
    });
  }

  // 5. 根据主键删除数据
  deleteData(key) {
    return new Promise((resolve, reject) => {
      if (!this.db) return reject('数据库未打开');

      const transaction = this.db.transaction([this.storeConfig.name], 'readwrite');
      const store = transaction.objectStore(this.storeConfig.name);

      const request = store.delete(key);
      request.onsuccess = () => resolve('删除成功');
      request.onerror = (e) => reject(`删除失败:${e.target.error.message}`);
    });
  }

  // 6. 关闭数据库
  closeDB() {
    if (this.db) {
      this.db.close();
      this.db = null;
    }
  }

  // 7. 删除数据库
  deleteDB() {
    return new Promise((resolve, reject) => {
      this.closeDB();
      const request = indexedDB.deleteDatabase(this.dbName);
      request.onsuccess = () => resolve('数据库删除成功');
      request.onerror = (e) => reject(`删除失败:${e.target.error.message}`);
    });
  }
}

2. 使用示例(增删改查)

运行

// 1. 初始化数据库(创建名为 userDB,版本1,对象仓库 user,主键 id,为 name 字段创建索引)
const dbHelper = new IndexedDBHelper('userDB', 1, {
  name: 'user',
  keyPath: 'id', // 主键为 id
  indexs: [{ field: 'name', unique: false }] // 为 name 字段创建非唯一索引
});

// 2. 打开数据库并执行操作
async function useDB() {
  try {
    // 打开数据库
    await dbHelper.openDB();

    // 新增/修改数据
    await dbHelper.putData({ id: 1, name: '张三', age: 20 });
    await dbHelper.putData({ id: 2, name: '李四', age: 22 });

    // 根据主键查询
    const user1 = await dbHelper.getDataByKey(1);
    console.log('查询id=1的用户:', user1); // 输出:{id:1, name:'张三', age:20}

    // 查询所有数据(筛选年龄>20的用户)
    const allUsers = await dbHelper.getAllData((user) => user.age > 20);
    console.log('年龄>20的用户:', allUsers); // 输出:[{id:2, name:'李四', age:22}]

    // 删除数据
    await dbHelper.deleteData(2);

  } catch (error) {
    console.error('操作失败:', error);
  } finally {
    // 关闭数据库(可选,浏览器会自动管理连接)
    // dbHelper.closeDB();
  }
}

// 执行操作
useDB();

三、关键特性与注意事项

  1. 异步操作

    • IndexedDB 所有操作都是异步的(避免阻塞主线程),需通过 Promise/async-await 处理回调;
    • 区别于 Web Storage 的同步操作,适合处理大量数据。
  2. 存储容量

    • 无固定上限(远大于 Web Storage 的 5-10MB),通常为浏览器磁盘空间的 50% 左右;
    • 存储大文件(如图片、音频)时,可使用 Blob 类型直接存储。
  3. 同源策略

    • 仅同源(协议 + 域名 + 端口)页面可访问同一数据库,保证数据隔离。
  4. 版本管理

    • 数据库版本号必须是整数,升级需递增版本号;
    • 只有版本升级时,onupgradeneeded 才会触发,可在此修改对象仓库 / 索引结构。
  5. 安全性

    • 数据存储在客户端,可被用户手动清除,不建议存储敏感数据
    • 受 XSS 攻击影响(恶意脚本可读写数据),需做好输入过滤。

四、IndexedDB vs Web Storage vs Cookie(核心对比)

特性IndexedDBWeb Storage (localStorage/sessionStorage)Cookie
存储容量超大(无固定上限)5-10MB约 4KB
操作方式异步(Promise / 回调)同步同步
数据类型支持任意类型(对象、Blob)仅字符串(需序列化)仅字符串
查询能力支持索引、游标、复杂筛选仅键值对查询(需遍历)仅键值对查询
网络传输仅本地存储,不参与传输仅本地存储,不参与传输随 HTTP 请求发送
适用场景大量结构化数据、离线应用简单键值对存储(偏好设置、临时状态)少量数据(登录态)

总结

  1. 核心定位:IndexedDB 是前端本地「大型数据库」,适合存储大量结构化数据、实现离线应用;
  2. 使用要点:操作异步且需通过事务执行,需封装 Promise 简化 API,版本升级需递增版本号;
  3. 选型建议:简单数据用 Web Storage,少量需传输数据用 Cookie,大量 / 复杂数据用 IndexedDB。