navigator.storage.getDirectory不能直接当数据库用,它仅提供FileSystemDirectoryHandle用于文件读写,需自行实现事务、查询、并发控制等逻辑;适合轻量级配置存储,复杂场景应结合sqlite-wasm等方案。

navigator.storage.getDirectory 本身不提供数据库功能,它只是获取一个可读写的 FileSystemDirectoryHandle,后续需自行实现结构化存储逻辑。想用它“做数据库”,本质是把 JSON、SQLite 或自定义索引文件存进该目录——但要注意:浏览器不会帮你做事务、查询、并发控制或崩溃恢复。
为什么不能直接当数据库用
原生文件系统 API(Native File System API)设计目标是“让用户可控地读写本地文件”,不是替代 IndexedDB 或 SQLite。它的核心能力只有:getFileHandle、getDirectoryHandle、resolve、removeEntry 等基础 I/O 操作。
常见误解是以为拿到 FileSystemDirectoryHandle 就能像 Node.js 的 fs 那样直接 writeFileSync + JSON.parse ——但实际:
- 所有读写必须走
FileSystemFileHandle.createWritable(),是异步流式写入,不能同步阻塞 - 没有原子重命名(
rename不可用),无法安全替换正在读的文件 - 无文件锁机制,多标签同时写同一文件会静默覆盖或报
"InvalidStateError" - 写入中途页面关闭 → 文件可能处于半写入状态,且无回滚手段
可行的轻量级私有存储方案(JSON + 单文件)
适合配置、缓存、草稿等低频、单用户、非关键数据。关键是绕过并发和一致性陷阱:
- 每次写入前先用
fileHandle.getFile()读取当前内容,合并后整体重写(避免增量更新) - 写入使用
createWritable({ keepExistingData: false })显式清空再写,防止残留字节 - 给文件加版本号或时间戳字段,读取时校验结构完整性(比如检查
data.version === EXPECTED_VERSION) - 不要在
beforeunload中触发写入——改用visibilitychange监听页面隐藏后再延迟 300ms 写入,降低中断风险
示例写入逻辑:
async function saveConfig(dirHandle, data) {
const fileHandle = await dirHandle.getFileHandle('config.json', { create: true });
const writable = await fileHandle.createWritable({ keepExistingData: false });
await writable.write(JSON.stringify({ ...data, savedAt: Date.now() }, null, 2));
await writable.close();
}
想支持查询或结构化?得自己搭索引层
如果需要按字段查、分页、模糊匹配,必须额外维护索引文件(如 index.json),并保证它与主数据文件的一致性:
- 写入主数据后,必须同步更新索引文件;两次写入之间若中断,索引就失效
- 推荐把索引和主数据合并到一个文件里(例如顶层
{ data: [...], index: { byTag: {}, byDate: [] } }),靠单次原子写入规避不一致 - 避免在内存中长期缓存整个数据集——大文件(>5MB)读取会阻塞主线程,应配合
AbortSignal和流式解析(如JSONStream类库) - Chrome 120+ 支持
FileSystemFileHandle.createSyncAccessHandle()(仅限安全上下文 + 本地文件协议),但目前仅限 Chromium,且仍需手动处理序列化/反序列化
真正该考虑 SQLite 的场景
如果你需要 ACID、多表关联、WHERE 查询、或已有 SQLite 数据迁移,别硬扛——用 sql-wasm + navigator.storage.getDirectory 组合更现实:
- 用
getDirectory获取目录句柄后,调用dirHandle.getFileHandle('db.sqlite', { create: true })得到文件句柄 - 传给
SQLiteDatabase.open()(来自@sqlite.org/sqlite-wasm)即可打开 WAL 模式数据库 - 注意:WAL 日志文件(
db.sqlite-wal)也必须由同一目录管理,否则跨会话丢失事务 - 首次加载时,
sqlite-wasm会尝试从文件恢复,但若db.sqlite被外部程序修改过,可能报"database disk image is malformed"——这是正常现象,说明你没控制好写入源头
最易被忽略的一点:用户一旦通过系统文件管理器手动删掉你用 getDirectory 保存的文件夹,所有句柄立即失效,后续 getFileHandle 调用会抛 "NotFoundError"。你得在每次操作前用 dirHandle.queryPermission({ mode: 'readwrite' }) 做存活检测,并准备好降级到 IndexedDB 的兜底路径。

















