人人都会AI编程

适用场景与基础操作

更新时间:2026-07-11

IndexedDB 是浏览器端真正的数据库,它不是 localStorage 的升级版,而是一个非关系型、异步、支持事务的键值对存储系统。理解什么场景该用它、怎么用它,是前端存储方案选型的关键一环。

适用场景:什么时候该用 IndexedDB

需要存储大量结构化数据

localStorage 只有 5-10MB 的容量(不同浏览器略有差异),超过就会报错。而 IndexedDB 的存储上限通常是浏览器可用磁盘空间的一定比例(可达几百 MB 甚至 GB),且支持复杂的数据结构(对象、数组、二进制等),无需序列化成字符串再存储。如果你的应用需要本地缓存大量数据(如离线小说阅读器、本地笔记应用、PWA 数据持久化),IndexedDB 是不二之选。

数据需要按条件查询,而非仅键值对

localStorage 只能通过 key 精确查找,不能模糊搜索、不能按字段排序、不能分页。IndexedDB 支持创建索引,可以对存储对象的某个字段进行查询、排序、范围过滤,类似于数据库的 WHERE、ORDER BY。例如,在邮件应用中按“已读/未读”筛选,或者在电商缓存中按“价格”排序,只有 IndexedDB 能胜任。

需要事务性和数据一致性

在操作多条相关数据时,如果突然发生错误(比如断电、标签页关闭),IndexedDB 的事务机制能保证要么所有操作全部完成,要么全部回滚,不会出现数据写了一半的不一致状态。localStorage 做不到这一点——你对多个 key 的写入是独立的,中间某一步出问题,前面的修改已经持久化了,无法回滚。

离线优先(Offline-First)或 PWA 应用

Service Worker 本身没有访问 DOM 的权限,但它可以操作 IndexedDB。这意味着可以在后台缓存网络请求结果、本地生成数据,等网络恢复后再同步到服务器。许多 PWA 的数据流都是“优先读写本地 IndexedDB,后台同步到云端”,以此实现离线可用。

不适用于

  • 少量简单的配置信息(用 localStorage 更方便)
  • 需要同步读写的场景(IndexedDB 是异步 API,没有同步版本,不能阻塞主线程)
  • 需要跨域共享数据(同源策略限制)
  • 需要存储敏感信息(IndexedDB 虽可存,但安全性不如加密后的 Cookie 或存放在后端)

基础操作:从打开数据库到读写数据

IndexedDB 的操作流程略微繁琐,需要先打开数据库,然后创建对象存储(类似数据库中的“表”),最后通过事务进行数据操作。下面展示最基础的使用模式。

1. 打开 / 创建数据库

// 打开名为 'MyDatabase' 的数据库,版本号为 1
const request = indexedDB.open('MyDatabase', 1);

request.onerror = (event) => {
  console.error('数据库打开失败', event.target.error);
};

request.onsuccess = (event) => {
  const db = event.target.result;
  console.log('数据库打开成功', db);
  // 之后的操作都在这里进行
};

// 首次创建或版本升级时触发
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  
  // 创建对象存储(相当于“建表”),设置主键
  if (!db.objectStoreNames.contains('notes')) {
    const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
    // 创建索引,可按 title 搜索、按 createdAt 排序
    store.createIndex('titleIndex', 'title', { unique: false });
    store.createIndex('createdIndex', 'createdAt', { unique: false });
  }
};

onupgradeneeded 只在数据库不存在或版本号高于现有版本时触发,这是定义数据结构的最佳时机。之后 onsuccess 拿到 db 实例,就可以开始读写。

2. 添加数据

function addNote(db, note) {
  // 开启一个读写事务,指定对象存储名称
  const transaction = db.transaction(['notes'], 'readwrite');
  const store = transaction.objectStore('notes');
  const request = store.add(note);

  request.onsuccess = () => console.log('笔记添加成功');
  request.onerror = (e) => console.error('添加失败', e.target.error);
}

任何读写操作都必须包裹在事务里,readwrite 表示可读写。多个事务可以并发执行,但修改同一对象存储的事务会排队。

3. 查询数据(按主键)

function getNote(db, id) {
  const transaction = db.transaction(['notes'], 'readonly');
  const store = transaction.objectStore('notes');
  const request = store.get(id);

  request.onsuccess = (e) => {
    if (request.result) {
      console.log('查询结果:', request.result);
    } else {
      console.log('未找到该笔记');
    }
  };
}

4. 通过索引查询

function getNotesByTitle(db, title) {
  const transaction = db.transaction(['notes'], 'readonly');
  const store = transaction.objectStore('notes');
  const index = store.index('titleIndex');
  const request = index.getAll(title);  // 获取所有匹配的笔记

  request.onsuccess = (e) => {
    console.log('标题匹配的笔记:', request.result);
  };
}

5. 更新与删除

function updateNote(db, updatedNote) {
  const transaction = db.transaction(['notes'], 'readwrite');
  const store = transaction.objectStore('notes');
  store.put(updatedNote);  // 如果 keyPath 已存在则更新,否则新增
}

function deleteNote(db, id) {
  const transaction = db.transaction(['notes'], 'readwrite');
  const store = transaction.objectStore('notes');
  store.delete(id);
}

基础操作小结

  • IndexedDB 所有操作都是异步的,依靠事件或回调获取结果,不会阻塞主线程。
  • 最核心的概念是数据库 → 对象存储 → 索引,一个数据库可以有多个对象存储,每个存储可以建多个索引。
  • 事务确保数据一致性,读写操作必须明确指定事务模式和涉及的对象存储。
  • 虽然原生 API 略显繁琐,但实际项目中常使用 Dexie.jsidb 这类轻量封装库,以 Promise 和链式调用的方式简化代码。

结合前面提到的 localStorage 和 Cookie,三种存储方案的能力边界一目了然:Cookie 适合少量身份信息且需随请求发送;localStorage 适合简单键值对持久化;而 IndexedDB 则是当你需要用“数据库的方式”在浏览器里管理数据时的唯一选择