文章

浏览器存储方案对比

浏览器存储方案对比

一句话概括

浏览器端四种存储方案——Cookie、localStorage、sessionStorage、IndexedDB——各有分工:Cookie 管身份传递,localStorage 管持久化偏好,sessionStorage 管会话隔离,IndexedDB 管海量结构化数据。选错方案比不会用更致命。

核心知识点

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. 四合一速查

 CookielocalStoragesessionStorageIndexedDB
容量4KB5-10MB5-10MB数百MB~磁盘60%
生命周期可设过期永久标签页关闭永久
作用域同源+path同源共享同源+同标签页同源共享
自动发请求
异步
结构化数据
事务

其实你每天都在用

  • 登录态sessionId 存在 HttpOnly Cookie 里,每次请求后台自动识别身份
  • 暗色模式切换theme: dark 写 localStorage,刷新页面不需要重新设
  • 表单草稿恢复:写了半小时的评论突然关掉标签页——localStorage 能救回来,sessionStorage 不行
  • Google Docs 离线编辑:所有文档内容存在 IndexedDB,断网照样编辑,联网后批量同步
  • 性能埋点暂存:用户行为数据先写 IndexedDB,攒够 50 条或每 30 秒批量上报,避免频繁 HTTP 请求

常见误解

  • ❌ 误区:「sessionStorage 能在同源的多个标签页间共享」 不能。这是它最根本的设计——每个标签页是独立 session。如果你需要在标签页间通信,用 BroadcastChannelstorage 事件。

  • ❌ 误区:「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 通常四个全用。

本文由作者按照 CC BY 4.0 进行授权