Skip to content

IndexedDB

IndexedDB 是浏览器提供的本地结构化存储方案,适合存储较大体积、需要索引查询或离线持久化的数据。相比 localStorage,它不是简单的字符串键值存储,而是一个异步的对象数据库。

适合什么场景

  • 离线缓存列表、草稿、表单数据
  • 存储大量结构化数据
  • 需要索引查询的数据
  • 前端需要做本地同步或断点恢复

和 Web Storage 的区别

对比项Web StorageIndexedDB
数据模型字符串键值对对象数据库
API 风格同步异步
数据量较小较大
查询能力很弱支持索引
适用场景简单配置、小状态离线数据、大量数据

核心概念

  • Database:数据库实例
  • Object Store:对象仓库,类似表
  • Transaction:事务,所有读写都在事务中进行
  • Index:索引,用于按字段查询

可以先简单记成:

  • 数据库存整体
  • 仓库存一类对象
  • 索引用来按字段查
  • 事务负责控制一次读写过程

打开数据库

js
const request = indexedDB.open('vfan-docs', 1)

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

request.onsuccess = () => {
  const db = request.result
  console.log('数据库打开成功', db)
}

indexedDB.open(name, version) 不会同步直接返回数据库实例,而是先返回一个打开请求对象。

初始化仓库

当数据库第一次创建或版本升级时,会触发 onupgradeneeded

js
const request = indexedDB.open('vfan-docs', 1)

request.onupgradeneeded = () => {
  const db = request.result

  if (!db.objectStoreNames.contains('articles')) {
    const store = db.createObjectStore('articles', {
      keyPath: 'id',
    })

    store.createIndex('title', 'title', { unique: false })
  }
}

这一阶段通常只做结构初始化或迁移,不负责日常读写。

写入数据

js
const request = indexedDB.open('vfan-docs', 1)

request.onsuccess = () => {
  const db = request.result
  const tx = db.transaction('articles', 'readwrite')
  const store = tx.objectStore('articles')

  store.put({
    id: 1,
    title: 'IndexedDB',
    content: 'local database',
  })

  tx.oncomplete = () => {
    console.log('写入完成')
  }
}

写入通常发生在 readwrite 事务里。

读取单条数据

js
const request = indexedDB.open('vfan-docs', 1)

request.onsuccess = () => {
  const db = request.result
  const tx = db.transaction('articles', 'readonly')
  const store = tx.objectStore('articles')
  const getRequest = store.get(1)

  getRequest.onsuccess = () => {
    console.log(getRequest.result)
  }
}

读取全部数据

js
const request = indexedDB.open('vfan-docs', 1)

request.onsuccess = () => {
  const db = request.result
  const tx = db.transaction('articles', 'readonly')
  const store = tx.objectStore('articles')
  const getAllRequest = store.getAll()

  getAllRequest.onsuccess = () => {
    console.log(getAllRequest.result)
  }
}

删除数据

js
const request = indexedDB.open('vfan-docs', 1)

request.onsuccess = () => {
  const db = request.result
  const tx = db.transaction('articles', 'readwrite')
  const store = tx.objectStore('articles')

  store.delete(1)
}

事务的意义

IndexedDB 的读写都发生在事务中:

  • readonly:只读事务
  • readwrite:读写事务

事务完成后会自动关闭,因此不要把事务打开后长时间挂着不继续操作,否则容易遇到“事务已失效”的问题。

版本升级

如果要新增仓库、修改索引或调整结构,需要提高版本号:

js
indexedDB.open('vfan-docs', 2)

然后在 onupgradeneeded 里做迁移逻辑。

这也是 IndexedDB 的一个重要边界:

  • 数据结构调整不是随时直接改
  • 往往要通过版本升级统一迁移

使用建议

  • 小数据优先 localStoragesessionStorage,不要为简单状态引入过重方案。
  • 需要离线缓存或大量结构化数据时,再考虑 IndexedDB
  • 原生 API 偏底层,实际项目里经常会配合 idb 之类的轻量封装库使用。
  • 设计数据结构时,先想清楚主键和索引,否则后续升级成本较高。

基于 MIT 许可发布