前端状态持久化:localStorage/sessionStorage/IndexedDB 选型

域名注册
域名注册 正式会员超兽战士 👑年卡会员
发布于 2026-10-07 22:35 ·4 浏览 ·0 回复

选型结论一句话:小于 100KB 的简单键值对(主题、语言、上次选择的筛选项)用 localStorage;只在当前标签页生命周期内有效的数据(多步表单草稿、向导进度)用 sessionStorage;超过 1MB、需要存对象/二进制、或要索引查询与事务的数据(离线草稿、图片缓存、消息列表)一律用 IndexedDB。三者不是替代关系,一个稍微像样的前端应用通常三个都用上。

localStorage、sessionStorage 和 IndexedDB 有什么区别?

结论:三者的核心差异在「容量、同步/异步、生命周期、能存什么类型」这四点上,容量差了两个数量级。

  • localStorage:每个源 5MB(Chrome、Firefox、Safari 一致),同步 API,只能存字符串,必须自己 JSON.stringify;数据跨会话、跨标签页共享,除非用户手动清理或代码删除,否则一直在。同步意味着写入会阻塞主线程,实测写入 1MB 字符串在主线程上要花几毫秒到十几毫秒,写大对象会直接掉帧。
  • sessionStorage:容量同样 5MB,同步 API,作用域是「标签页 + 源」。刷新页面数据还在,关掉标签页就没了;同一个页面开两个标签互不可见。有个坑:在 Chrome 里用「复制标签页」打开时,新标签会复制一份 sessionStorage 内容。
  • IndexedDB:异步 API,容量按磁盘比例给,Chrome 单个源最多可用磁盘空间的 60%,Firefox 是 10%(上限 10GiB),Safari 更保守。它能通过结构化克隆直接存对象、数组、Blob、File、ArrayBuffer,支持索引、游标、事务和版本迁移,代价是 API 啰嗦,所以实践中用 idb(约 1KB)或 Dexie.js 包装。

顺带排除一个选项:Cookie 只有 4KB,且每次 HTTP 请求都会带上,不适合做状态持久化,它的位置是 httpOnly 的登录凭证。

什么时候必须用 IndexedDB,不能用 localStorage?

结论:满足「单条数据 > 100KB」「总量 > 1MB」「需要按字段查询」「要存 Blob / File / ArrayBuffer」「写入频率高于每秒 1 次」中任意一条,就应该选 IndexedDB。

典型场景有三个。一是富文本或代码编辑器的离线草稿,一份文档加上历史版本轻松过 MB,塞 localStorage 会在每次自动保存时卡住输入。二是图片、音频、PDF 的缓存,localStorage 存不了二进制,转 base64 会膨胀约 33%,双重浪费。三是需要按条件查的数据,比如「取最近 20 条未同步的记录」,localStorage 只能取出整个数组再在内存里过滤,IndexedDB 建个 index 就能直接游标遍历。

用 idb 打开数据库的最小代码是这样:

import { openDB } from 'idb';
const db = await openDB('app', 1, {
  upgrade(db) { db.createObjectStore('docs', { keyPath: 'id' }); }
});
await db.put('docs', { id: 1, title: '草稿', updatedAt: Date.now() });

注意 IndexedDB 的版本号只能是整数且不能降级,删库要用 indexedDB.deleteDatabase();升级逻辑必须写在 upgrade 回调里,线上加字段时记得写迁移分支。

localStorage 有哪些容易踩的坑?

结论:localStorage 有两个硬性坑——浏览器可能直接让它抛异常,以及它只在「其它标签页」触发 storage 事件。

第一个坑:Safari 无痕模式下 setItem 会抛 QuotaExceededError;页面被嵌在 iframe 里且浏览器开启第三方 Cookie 拦截时,访问 window.localStorage 本身会抛 SecurityError。所以所有读写都要包 try/catch,失败时降级到内存对象,别让整个应用白屏。

第二个坑:window.addEventListener('storage', handler) 在写入的那个标签页里不触发,只在其它同源标签页触发,用它做跨标签同步时要补一条本页内的状态更新。另外建议所有 key 统一加前缀和版本号,比如 app:v2:theme,将来结构变更时按前缀批量清理旧数据比逐条判断可靠。

状态持久化怎么选,有没有通用决策流程?

结论:按「数据大小 → 生命周期 → 是否二进制 → 写入频率」四步走,30 秒就能定下来。

  1. 估算单条 JSON 序列化后的字节数,以及总条数。
  2. 问一句「关掉标签页还要不要」:不要 → sessionStorage,要 → 继续。
  3. 总量小于 100KB 且只是扁平键值对 → localStorage;否则 → IndexedDB。
  4. 需要存 Blob / File,或每秒写入多次 → 直接 IndexedDB,别再犹豫。

工程上常见的做法是抽象一层 storage 接口,底层按上述规则路由到不同实现,业务代码只调 await storage.set(key, value)。上层接口统一成异步(返回 Promise),将来从 localStorage 换成 IndexedDB 时业务代码一行都不用改——这是唯一值得提前做的架构决策。另外无论选哪种,容量都要用 navigator.storage.estimate() 监控,需要长期保留的数据再调 navigator.storage.persist() 申请持久化,否则浏览器在磁盘紧张时可能清理掉 IndexedDB 数据。

总结:localStorage 管 100KB 以内的扁平配置,sessionStorage 管单标签页的临时状态,IndexedDB 管大容量、二进制和需要查询的结构化数据;三者的读写都包好异常处理和版本迁移,接口统一成异步,后续替换成本几乎为零。

版权声明:本文来自 GJ站长论坛《前端状态持久化:localStorage/sessionStorage/IndexedDB 选型》
原文链接:https://www.gj0.com/thread-593.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~