IndexedDB:面向 PWA 的结构化客户端存储
发布于
一句话: IndexedDB 是浏览器中用于结构化数据的事务型、异步数据库——JavaScript
对象、文件与 blob——按键存储并建立索引以供查找。它是 PWA 离线应用数据的正确存储:比
localStorage 大得多也丰富得多,可在 service worker 内工作,并在受源存储配额约束的前提下
跨会话保留。
何时该用 IndexedDB
Section titled “何时该用 IndexedDB”localStorage是一个同步的、键与值均为字符串的存储,面向少量数据——存放几个标志位 尚可,存放应用数据则不当,且它不向 service worker 暴露。- Cache Storage 为离线资源存储
Request/Response对——是应用外壳,而非你的记录。 - IndexedDB 保存结构化记录与二进制 blob,是异步的(永不阻塞主线程),且窗口与 service worker 都可访问——离线草稿、排队的变更、缓存的 API 数据与大型媒体的归宿。
- 数据库(Database) —— 命名、带版本,限定在单一源内。
- 对象存储(Object store) —— 类似表;保存以键路径或生成键作为键的记录(任何可结构化 克隆的值)。
- 索引(Index) —— 对所存记录某个属性的二级查找,让你能用主键以外的东西查询。
- 事务(Transaction) —— 每次读写都发生在事务内,限定于命名的存储与一种模式
(
readonly/readwrite)。一旦其关联请求完成、控制权返回事件循环,事务便自动提交 (正常使用中你不会调用commit())。 - 请求(Request) —— 每个操作返回一个
IDBRequest,触发onsuccess/onerror; 结果到达事件上,而非作为返回值。
异步请求模型
Section titled “异步请求模型”原生 API 基于事件,这是它最锋利的边缘。一个常见的坑是:事务只在仍有挂起请求时存活——在
操作之间 await 一个无关的 promise,会让控制权返回事件循环,使事务在你不知情时提交。
const open = indexedDB.open('app', 2);
open.onupgradeneeded = (event) => { const db = event.target.result; // schema 变更仅在此处、versionchange 事务内合法。 if (!db.objectStoreNames.contains('drafts')) { const store = db.createObjectStore('drafts', { keyPath: 'id' }); store.createIndex('byUpdated', 'updatedAt'); }};
open.onsuccess = (event) => { const db = event.target.result; const tx = db.transaction('drafts', 'readwrite'); tx.objectStore('drafts').put({ id: 'd1', updatedAt: Date.now(), body: '…' }); tx.oncomplete = () => console.log('saved'); tx.onerror = () => console.error('write failed', tx.error);};带版本的 schema 升级
Section titled “带版本的 schema 升级”版本号是 IndexedDB 做迁移的方式。以高于已存储版本的版本号打开会触发 onupgradeneeded,
而该回调是唯一允许创建或删除对象存储与索引的地方。关键规则:
- schema 变化时提升版本号;按
event.oldVersion分支,依序应用每个迁移步骤。 - 当其他标签页仍以旧版本持有数据库时,
versionchange升级会被阻塞——监听blocked/versionchange事件并关闭陈旧连接,让升级得以进行。 - 普通
readwrite事务中禁止 schema 操作;尝试这样做会抛出异常。
配额、持久化与逐出
Section titled “配额、持久化与逐出”IndexedDB 与 Cache Storage 等一起,共享由 Storage API 管理的单一每源存储配额。默认情况下
存储是尽力而为的,可能在存储压力下被清除,因此对持久性关键的数据应让 IndexedDB 搭配
navigator.storage.persist() 申请以请求持久存储。为写入包裹捕获 QuotaExceededError,
随后清理陈旧数据并重试,而非直接硬失败。(完整的配额与逐出模型见存储持久化参考。)
浏览器与生态支持
Section titled “浏览器与生态支持”IndexedDB 在当前主流浏览器上获得支持,且窗口与 service worker 都可访问,使其成为支持离线的
PWA 的基线持久存储。由于原始事件 API 冗长且易误用,许多应用会用一层轻量的基于 promise 的
封装来包裹它——所引 web.dev 指南使用 idb 库,它在保持此处所述底层语义的同时简化了 API。
- 用 IndexedDB 存放结构化/离线数据;把
localStorage留给极小的同步标志位。 - 在
onupgradeneeded内、依据版本号完成所有createObjectStore/createIndex工作。 - 不要在事务进行中
await无关 promise——那会让事务提前自动提交。 - 处理请求与事务上的
onerror;让失败浮现,而非默默丢弃写入。 - 为你实际运行的查询定义索引,而非扫描每条记录。
- 捕获
QuotaExceededError、清理并重试;对持久性关键的数据调用persist()。 - 考虑用
idb这类小型的基于 promise 的封装,以免自行重新实现事件 API。