indexeddb 本身不解决离线存储,需与 service worker 和 cache api 配合;关网后 open 报错主因是 https 缺失、无用户交互、隐私模式或非法数据库名;必须先缓存 html 等核心资源,再用 indexeddb 存数据;同步需状态管理、幂等接口与分片处理;ios safari 有 50mb 限制和手势触发要求。

IndexedDB 本身不解决“离线存储”问题,它只解决“本地结构化数据存哪、怎么查”的问题;真正让页面离线可用的,是它和 Service Worker + Cache API 的配合方式。
IndexedDB.open() 为什么关网后还报错?
常见错误现象:Uncaught DOMException: Failed to execute 'open' on 'indexedDB': access is denied 或直接静默失败。
这不是网络问题,而是浏览器策略拦截:
- 页面没通过 HTTPS 加载(localhost 除外),部分浏览器(如 Safari)会直接拒绝 IndexedDB
- 用户从未与页面交互(比如自动启动的 PWA 启动页),iOS Safari 可能静默降级或拒绝打开
- 无痕/隐私模式下,IndexedDB 是临时的,关闭窗口即清空,且首次写入可能被限制
- 数据库名含非法字符(如斜杠、空格)或版本号非正整数,
indexedDB.open("my-db/v1", 1)就会失败
正确做法:在用户点击、触摸等手势后首次调用 indexedDB.open();用 navigator.storage.estimate() 预检配额;对 onblocked 和 onerror 做显式处理,别依赖 onsuccess。
Service Worker 不注册,IndexedDB 再多也没用
典型误区:写了 indexedDB.open(),也存了草稿,但关网刷新页面就白屏——因为 HTML/CSS/JS 根本没加载进来。
Service Worker 是离线入口,必须先让它缓存核心资源:
- 注册 SW 必须在页面加载完成后的 JS 中执行,不能放在
defer脚本里却没加await navigator.serviceWorker.register()判断 - Cache API 缓存的是请求-响应对,不是文件路径;
cache.add("/app.css")本质是发一次 GET 请求再存响应体 - HTML 文件必须显式缓存(如
cache.addAll(["/", "/index.html"])),否则关网后连 JS 都跑不起来,indexedDB.open()压根不会执行 - 不要在 SW 的
fetch事件里对所有请求都 fallback 到 cache;未缓存的 API 请求应透传或返回 503,避免掩盖真实错误
一句话:没有 SW,IndexedDB 是一座孤岛;没有缓存 HTML,这座岛连船都开不过去。
离线写入后怎么同步?别靠定时轮询
常见错误:监听 online 事件一触发就立刻遍历所有 pending 记录,全量提交,结果服务端重复插入、前端事务中断、状态错乱。
健壮同步的关键设计点:
- 每条记录加字段:
syncStatus: "pending" | "syncing" | "success" | "failed",状态变更必须走事务 - 同步逻辑应封装为独立函数,由
online事件 + 定时检查(如每 30s)双触发,避免漏掉短暂联网窗口 - fetch 失败时,
catch中只更新状态为"failed",不删除原始数据;重试前先查syncStatus === "failed",避免覆盖新数据 - 服务端必须幂等:例如 PostgreSQL 用
INSERT ... ON CONFLICT DO NOTHING,或用客户端生成唯一 ID(如 UUID v4)避免主键冲突 - 避免在 UI 线程做批量同步;用 Web Worker 或
setTimeout(..., 0)分片处理,防止阻塞渲染
同步不是“连上网就全发一遍”,而是“每次只发一批、带状态、可中断、可重入”。
移动端特别是 iOS Safari 的硬限制必须提前测
iOS Safari 对 IndexedDB 有两道硬门槛:单库 50MB 上限、首次写入需用户手势触发。很多问题线上才暴露,因为开发机用的是 Chrome。
实操建议:
- 真机测试必须包含 iOS Safari(非模拟器),尤其关注地图瓦片、富文本附件等大体积场景
- 写入前必调
navigator.storage.estimate(),当usage / quota > 0.75时主动清理旧数据(如按timestamp游标删除) - 瓦片类数据优先用
ArrayBuffer存,不用Blob;读取时用URL.createObjectURL(new Blob([buf], {type}))生成 src,避免 canvas 污染 - 数据库升级时,
onupgradeneeded里建好索引,但查询逻辑别写在onsuccess里——旧标签页可能还在用老 schema,直接查会报错
最易忽略的点:不是“能不能存”,而是“存完之后,下次打开时还能不能安全读”。schema 升级、跨标签页阻塞、内存压力丢数据——这些异常比容量超限更常导致离线功能静默失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











