IndexedDB 必须通过 JavaScript 异步 API 初始化,首次或版本升级时需在 onupgradeneeded 中创建对象仓库,否则 db 为空、事务报错;keyPath 与 autoIncrement 需按主键来源精准配置;事务不可复用,必须每操作新建并监听 oncomplete 才确保落盘。

IndexedDB 不能在 HTML 中“直接创建”——它必须通过 JavaScript API 在运行时打开或初始化,且所有操作都得走异步事件流。硬写在 HTML 标签里(比如 <script></script> 里没逻辑、没错误处理)大概率打不开库,甚至静默失败。
为什么 indexedDB.open() 必须配 onupgradeneeded 才能建结构?
IndexedDB 的 schema(对象仓库、索引)只能在数据库版本升级时定义,而首次打开新库、或传入更高版本号时,浏览器会强制触发 onupgradeneeded。漏掉这个回调,createObjectStore() 就根本不会执行——你拿到的 db 实例里空空如也,后续任何 transaction 都会报 "InvalidStateError: Failed to execute 'transaction' on 'IDBDatabase': The database has not been upgraded yet."。
常见错误现象:
- 控制台没报错,但
db.objectStoreNames.length === 0 - 调用
transaction('users', 'readwrite')时抛出NotFoundError
实操建议:
- 每次修改结构(新增 store / index / 改 keyPath),必须递增版本号(比如从
1→2) - 在
onupgradeneeded里先检查db.objectStoreNames.contains('storeName'),避免重复创建报错 - 不要在
onsuccess里尝试建 store——此时事务已关闭,createObjectStore会直接 throw
createObjectStore() 的 keyPath 和 autoIncrement 怎么选?
这是决定数据主键行为的关键参数,选错会导致写入失败或语义混乱。
使用场景:
- 你有明确业务 ID(如后端返回的
user.id)→ 用{ keyPath: 'id' },不设autoIncrement - 纯前端生成、无需外部 ID(如本地草稿、日志条目)→ 用
{ keyPath: 'id', autoIncrement: true },让 IndexedDB 自动填数字 - 完全无主键字段(比如存纯 Blob 或 JSON 片段)→ 只能用
add(item, key)显式传 key,此时keyPath留空或不设
容易踩的坑:
- 设了
keyPath: 'id'却传入{ name: 'Alice' }(没id字段)→add()报DataError - 设了
autoIncrement: true还手动传id→ 浏览器忽略你传的id,仍自增 - 改
keyPath但没升版本号 →onupgradeneeded不触发,旧结构沿用,新字段无法作为 key
事务(IDBTransaction)为什么不能跨函数复用?
IndexedDB 的事务生命周期极短:一旦所有待定请求(add、get 等)完成或出错,事务自动关闭。把它存在变量里、之后再 objectStore(),大概率遇到 "InvalidStateError: Transaction is inactive or scope is not valid"。
性能与兼容性影响:
- 事务关闭后还调
store.add()→ 操作直接丢弃,无提示 - 在
onsuccess里开新事务是安全的,但别试图“缓存”事务实例 - Safari 对长时间未完成的事务更敏感,可能提前 abort,所以读写尽量集中、别拆太碎
实操建议:
- 每个原子操作(增/删/查)都新建事务:
db.transaction('users', 'readwrite') - 批量写入用单个事务包住多个
add(),比循环开 10 个事务快 3–5 倍 - 只读查询用
'readonly',避免锁表,尤其在多标签页共用 DB 时
离线数据写入后,怎么确保它真被存进去了?
IndexedDB 的写入不是“发出去就完事”,它依赖事务的 oncomplete 或请求的 onsuccess 才算落地。很多人只监听 add().onsuccess,却忽略事务级确认,导致网络恢复后同步逻辑误判“本地已写”。
关键点:
-
store.add().onsuccess只表示“已加入事务队列”,不代表落盘 - 真正可靠的信号是
transaction.oncomplete—— 此时数据已持久化 - 如果要链式操作(如“写日志 → 更新状态”),必须等前一个事务
oncomplete再开下一个
示例片段:
function saveWithGuarantee(data) {
const tx = db.transaction('logs', 'readwrite');
const store = tx.objectStore('logs');
store.add(data);
return new Promise((resolve) => {
tx.oncomplete = () => resolve(); // ✅ 这才是落盘确认
tx.onerror = () => reject(tx.error);
});
}
最容易被忽略的地方:事务没有 onerror 处理时,错误会被吞掉;而 oncomplete 在任意子请求失败时也不会触发——所以必须同时监听两者,否则离线写入成功与否将变成玄学。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











