面对 Cookie、localStorage、sessionStorage、IndexedDB 等多种浏览器存储方案,如何在不同的业务场景下做出正确的选择?本节通过对比核心特性,并总结经过验证的最佳实践,帮助你在开发中避免数据丢失、安全漏洞和性能陷阱。
17.4.1 四种存储方案核心对比
| 特性 | Cookie | localStorage | sessionStorage | IndexedDB |
|------|--------|--------------|----------------|-----------|
| 容量上限 | 约 4KB(单个域名下) | 约 5MB | 约 5MB | 通常为数百 MB 甚至更大(取决于浏览器和磁盘空间) |
| 生命周期 | 可设置过期时间(Expires/Max-Age);未设置则为会话期,关闭浏览器即失效 | 永久存储,除非手动删除或清除浏览器数据 | 仅当前会话页签有效,关闭页签即清除 | 永久存储,手动删除或清除浏览器数据 |
| 作用域 | 同源共享,可通过 domain/path 控制范围 | 同源所有标签页共享 | 同源且同标签页隔离,不同标签页独立 | 同源共享 |
| 是否随 HTTP 请求发送 | 是,每次请求自动附加到请求头中,可能影响性能 | 否,仅存在于客户端,纯 JavaScript 操作 | 否 | 否 |
| API 难易程度 | 字符串操作,读写麻烦(document.cookie) | 简单的键值对 API,同步操作 | 同 localStorage | 异步 API,基于事件或 Promise,较为复杂 |
| 存储类型 | 仅字符串 | 仅字符串(通常配合 JSON 序列化) | 仅字符串 | 支持字符串、二进制数据(Blob、ArrayBuffer、文件等)、结构化克隆数据 |
| 安全性 | 可通过 HttpOnly, Secure, SameSite 属性增强,但整体易受 XSS 攻击 | 易被 XSS 攻击读取 | 同 localStorage | 同 localStorage,但可配合透明加密方案 |
| 典型用途 | 身份标识(Session ID)、少量跨请求携带的信息 | 主题偏好、用户配置、少量不敏感的数据 | 表单草稿、临时状态(如页面间传递敏感数据但不想持久化) | 离线应用数据、缓存大文件、复杂的客户端数据存储 |
17.4.2 如何选择:一张决策流程图
- 数据需要自动发送给服务器吗?(如认证令牌)
→ 是,且数据量极小(<4KB)→ 使用 Cookie(注意设置 HttpOnly、Secure、SameSite 属性以防御攻击)。
→ 否,进入下一步。
- 数据是否需要跨标签页共享?
→ 需要,且希望持久化 → localStorage。
→ 不需要,仅当前标签页有效 → sessionStorage(例如表单填写一半,刷新页面仍可恢复)。
→ 不需要,且随标签页关闭即废弃 → sessionStorage。
- 数据是否超过 5MB 或需要存储非字符串类型(如文件、Blob、二进制数据)?
→ 是 → IndexedDB。
→ 否,且使用简单键值对即可 → 继续使用 localStorage / sessionStorage。
- 数据需要执行复杂查询或事务管理吗?
→ 是 → IndexedDB(支持索引、游标、事务)。
17.4.3 真实场景下的最佳实践
✅ Cookie 使用原则
- 保持体积最小:只存储必要的少量数据(如 Session ID),避免每次请求携带大量无关信息影响网络性能。
- 开启安全属性:设置
HttpOnly(防止 JavaScript 读取)、Secure(仅 HTTPS 传输)、SameSite=Lax/Strict防止 CSRF。 - 不要用 Cookie 存储复杂结构化数据:Cookie 的读写和解析开销大,且容量有限。
✅ localStorage / sessionStorage 使用原则
- 始终使用 JSON 序列化/反序列化:
localStorage.setItem('user', JSON.stringify(userObj));
const user = JSON.parse(localStorage.getItem('user'));
- 敏感数据不要放入 localStorage / sessionStorage:它们无法设置 HttpOnly,任何同源的 JavaScript 都可读取,XSS 攻击可轻易截获。
- 注意同步 API 的性能:读写操作会阻塞主线程,如果数据量极大(如超过 1MB),建议改用 IndexedDB。
- 利用 sessionStorage 保存临时状态:如多步骤表单的中间数据、SPA 路由切换时不想重新请求的数据。
- 监听 storage 事件实现跨标签页通信:当其他标签页修改 localStorage 时,当前页会触发
window.addEventListener('storage', ...),可用于同步登录状态、主题等。
✅ IndexedDB 使用原则
- 处理浏览器配额限制:虽然容量很大,但不是无限的。对于较大的数据,要考虑清理策略或提示用户。
- 封装原生 API:原生的 IndexedDB 基于事件,API 较繁琐。推荐使用封装库,如
idb(Google 提供)、Dexie.js,它们提供更友好的 Promise 接口。 - 合适的索引设计:根据查询方式建立索引,否则需要遍历全库,性能极差。
- 理解事务生命周期:读写操作必须在事务内完成,事务会自动提交,不能在异步回调间保持事务活跃(这点与 SQL 数据库不同,需要特别小心)。
- 适合存储离线 App 数据:如 PWA 缓存大量文章、图片元数据,支持离线搜索。
17.4.4 常见不当用法与纠正
| 不当做法 | 后果 | 改进方案 |
|---------|------|----------|
| 用 localStorage 存储用户密码或 Token | XSS 攻击导致凭证泄露 | Token 放在 httpOnly 的 Cookie 中,或内存变量中,避免持久化 |
| 将大量 JSON 数据反复写入 localStorage | 频繁同步操作导致页面卡顿 | 改用 IndexedDB 或使用内存缓存,减少磁盘 I/O |
| 未实施 JSON.stringify/parse 导致类型错误 | 存数字取出来变成字符串,"true" !== true | 统一序列化/反序列化函数,必要时加校验 |
| 在同一个源下使用 localStorage 作为跨标签页通信的唯一手段 | storage 事件不会在当前标签页触发,可能丢失消息 | 配合 BroadcastChannel API 或 window.postMessage |
| 滥用 Cookie 传输大量数据 | 降低每个请求的速度,尤其在移动网络环境 | 仅保留关键标识,其余数据走 API |
17.4.5 存储方案的演进视角
随着 Web 应用能力的增强,存储方案也在不断丰富。现代 Web 还提供了 Cache API(用于 Service Worker 缓存 HTTP 请求/响应)和 File System Access API(读写用户本地文件),它们与上述存储互补。一个成熟的 PWA 应用通常会同时使用 IndexedDB(结构化数据)和 Cache API(离线资源缓存)。但基础选择同样重要:从安全、容量、生命周期三个维度出发,始终能找到最合适的方案。