浏览器存储方案对比
一句话概括
浏览器端四种存储方案——Cookie、localStorage、sessionStorage、IndexedDB——各有分工:Cookie 管身份传递,localStorage 管持久化偏好,sessionStorage 管会话隔离,IndexedDB 管海量结构化数据。选错方案比不会用更致命。
核心知识点
1. Cookie — 自动携带的身份牌(4KB)
1
2
3
4
5
6
7
8
// 后端 Set-Cookie,浏览器自动带
// Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600
// 前端只能读写非 HttpOnly 的
document.cookie = 'theme=dark; Max-Age=2592000; Path=/';
console.log(document.cookie); // "theme=dark; lang=zh"
// 经典误区:document.cookie 读出来是所有 cookie 拼成的字符串,不是对象
特点: 每次 HTTP 请求自动携带、4KB 上限、可设 HttpOnly 防 XSS 窃取。它的定位从来不是”存数据”,而是”无状态 HTTP 的身份补丁”。
2. localStorage — 永久化键值对(~5MB)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 存取(只能存字符串)
localStorage.setItem('prefs', JSON.stringify({ theme: 'dark', lang: 'zh' }));
const prefs = JSON.parse(localStorage.getItem('prefs'));
// 带过期时间封装(面试高频题)
const storage = {
set(key, value, ttlMs) {
localStorage.setItem(key, JSON.stringify({ value, expiry: Date.now() + ttlMs }));
},
get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const { value, expiry } = JSON.parse(raw);
return Date.now() > expiry ? (localStorage.removeItem(key), null) : value;
}
};
// 监听同源其他标签页的变化(自身页面不会触发)
window.addEventListener('storage', ({ key, oldValue, newValue }) => {
if (key === 'token') window.location.reload();
});
特点: 同源共享、同步 API(大 value 会卡主线程)、永久存储。适合主题、用户偏好、不敏感的 token。
3. sessionStorage — 标签页级隔离(~5MB)
1
2
3
4
5
6
7
8
// API 和 localStorage 完全一致
sessionStorage.setItem('draft', JSON.stringify({ title: '草稿' }));
// 关键行为:A 标签页写入,B 标签页(同源!)读不到
// 标签页A
sessionStorage.setItem('key', 'A');
// 标签页B
console.log(sessionStorage.getItem('key')); // null
特点: 每个标签页独立一份。从 window.open() 打开的页面会复制初始快照,但后续各自独立。适合表单草稿、一次性 token。
4. IndexedDB — 浏览器内置 NoSQL(数百 MB ~ GB)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 idb 库(推荐),告别原生回调地狱
import { openDB } from 'idb';
const db = await openDB('AppDB', 1, {
upgrade(db) {
const store = db.createObjectStore('users', { keyPath: 'id' });
store.createIndex('name', 'name');
}
});
// 写入
await db.add('users', { id: 1, name: 'Alice', avatar: blob });
// 按索引查询
const user = await db.getFromIndex('users', 'name', 'Alice');
// 游标遍历
for await (const cursor of db.transaction('users').store) {
console.log(cursor.value);
}
特点: 异步不阻塞 UI、支持事务、索引、结构化克隆(能存 File/Blob)。PWA 离线缓存首选。
5. 四合一速查
| Cookie | localStorage | sessionStorage | IndexedDB | |
|---|---|---|---|---|
| 容量 | 4KB | 5-10MB | 5-10MB | 数百MB~磁盘60% |
| 生命周期 | 可设过期 | 永久 | 标签页关闭 | 永久 |
| 作用域 | 同源+path | 同源共享 | 同源+同标签页 | 同源共享 |
| 自动发请求 | ✅ | ❌ | ❌ | ❌ |
| 异步 | ❌ | ❌ | ❌ | ✅ |
| 结构化数据 | ❌ | ❌ | ❌ | ✅ |
| 事务 | ❌ | ❌ | ❌ | ✅ |
其实你每天都在用
- 登录态:
sessionId存在 HttpOnly Cookie 里,每次请求后台自动识别身份 - 暗色模式切换:
theme: dark写 localStorage,刷新页面不需要重新设 - 表单草稿恢复:写了半小时的评论突然关掉标签页——localStorage 能救回来,sessionStorage 不行
- Google Docs 离线编辑:所有文档内容存在 IndexedDB,断网照样编辑,联网后批量同步
- 性能埋点暂存:用户行为数据先写 IndexedDB,攒够 50 条或每 30 秒批量上报,避免频繁 HTTP 请求
常见误解
❌ 误区:「sessionStorage 能在同源的多个标签页间共享」 不能。这是它最根本的设计——每个标签页是独立 session。如果你需要在标签页间通信,用
BroadcastChannel或storage事件。❌ 误区:「localStorage 可以存 token」 可以,但不安全。任何注入的恶意 JS 脚本都能
localStorage.getItem('token')。token 应存 HttpOnly Cookie(防 XSS 读取)或仅在内存中持有(SPA 刷新重登录)。❌ 误区:「localStorage 不够用就无脑切 IndexedDB」 IndexedDB 异步操作带来了代码复杂度的大幅提升。5MB 以内用 localStorage 封装 + 过期管理就够了。还需要存 Blob/File、需要索引查询、需要事务一致性时,再上 IndexedDB。
❌ 误区:「sessionStorage 关掉浏览器就没了」 准确说是关标签页。如果浏览器崩溃恢复标签页,sessionStorage 可能还在——浏览器厂商对此行为的实现不完全一致,别依赖这个。
一句话总结
选型决策树:需要随请求自动发 → Cookie;小数据长期存 → localStorage;临时隔离 → sessionStorage;大且结构化 → IndexedDB。四种方案不是互斥的,线上 App 通常四个全用。