文章

浏览器存储方案对比深度解析

面试常考浏览器存储全家桶:localStorage、sessionStorage、Cookie、IndexedDB 的容量、生命周期、同源与读写差异。 讲清各自适用场景与选型,能答出「什么时候用哪个」即可过关。

浏览器存储方案对比深度解析

一句话概括

Cookie 不是”存储”,是 HTTP 无状态的身份补丁——它设计出来就一个使命:让每次请求自动带上身份信息。localStorage 存偏好,sessionStorage 管隔离,IndexedDB 当数据库。四个全用、各司其职,才是上线应用的常态。

核心知识点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Cookie 的价值是 Transport(自动随请求传输),不是 Storage(存数据)
// 后端种身份 Cookie:
// Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600; Path=/

// 前端只能读写非 HttpOnly 的 Cookie
document.cookie = 'theme=dark; Max-Age=2592000; Path=/; Secure; SameSite=Lax';

// ⚠️ 经典面试坑:document.cookie 返回的是拼起来的字符串,不是对象!
console.log(document.cookie); // "theme=dark; lang=zh"

// 手写解析(注意 value 里可能有 =,用 indexOf 定位第一个 = 切分)
const parseCookies = str =>
  Object.fromEntries(
    str.split('; ').map(pair => {
      const i = pair.indexOf('=');
      return [pair.slice(0, i), pair.slice(i + 1)];
    })
  );

核心定位: Cookie 的 4KB 不是 bug,是 feature——它应该只存 sessionId,几十字节足够。存多了每次 HTTP 请求都带着,白白拖慢所有请求。

2. localStorage — 持久化键值对(~5MB),同步 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 同源所有标签页共享、永久存在、同步读写
localStorage.setItem('prefs', JSON.stringify({ theme: 'dark', fontSize: 16 }));

// 🔥 面试高频:给 localStorage 加过期时间
const smartStorage = {
  set(key, value, ttlMs = Infinity) {
    localStorage.setItem(key, JSON.stringify({
      value,
      expires: Date.now() + ttlMs
    }));
  },
  get(key) {
    const raw = localStorage.getItem(key);
    if (!raw) return null;
    const { value, expires } = JSON.parse(raw);
    if (Date.now() > expires) { localStorage.removeItem(key); return null; }
    return value;
  }
};

// storage 事件:其他标签页写入时触发(自己写自己不触发)
window.addEventListener('storage', ({ key, newValue }) => {
  if (key === 'auth_token' && !newValue) {
    // 另一个标签页退登了,本标签页跟着刷新
    location.reload();
  }
});

3. sessionStorage — 标签页级沙箱(~5MB)

1
2
3
4
5
6
7
8
sessionStorage.setItem('draft', JSON.stringify({ title: '草稿', content: '' }));

// 核心行为演示:
// 标签页 A:sessionStorage.setItem('x', 'A')
// 标签页 B(同源同 URL):sessionStorage.getItem('x') // null!
//
// ⚠️ 注意:Ctrl/Cmd+N 新窗口 = 全新 session,不继承任何 sessionStorage
// Chrome 崩溃恢复标签页会恢复 sessionStorage,Firefox 不一定——别依赖这个

4. IndexedDB — 浏览器里的异步数据库(数百 MB+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 原生 API 回调地狱,生产用 idb 库(仅 1.3KB)
import { openDB } from 'idb';

const db = await openDB('MyApp', 1, {
  upgrade(db) {
    const store = db.createObjectStore('messages', {
      keyPath: 'id',
      autoIncrement: true
    });
    store.createIndex('by_time', 'timestamp');
  }
});

// 写入
await db.add('messages', { text: 'Hello', timestamp: Date.now() });

// 索引查询:最近 24 小时的消息
const recent = await db.getAllFromIndex('messages', 'by_time',
  IDBKeyRange.lowerBound(Date.now() - 86400000));

什么时候用 IndexedDB 而非 localStorage:

  • 数据 > 几十 MB(localStorage 硬上限 ~5MB)
  • 需要按条件查询(localStorage 只能全量遍历 Object.keys)
  • 存 Blob / File / ArrayBuffer(localStorage 只能字符串)
  • 需要事务保证写入原子性

5. 现代补充:CacheStorage 和 OPFS

1
2
3
4
5
6
7
8
9
// CacheStorage:Service Worker 专用,缓存静态资源,PWA 秒开靠它
const cache = await caches.open('v1');
await cache.addAll(['/', '/styles.css', '/app.js']);

// OPFS(Origin Private File System):大文件高性能读写
// SQLite WASM、视频编辑器都在用
const root = await navigator.storage.getDirectory();
const fileHandle = await root.getFileHandle('data.bin', { create: true });
const writable = await fileHandle.createSyncAccessHandle(); // 同步写,极快

决策速查

 CookielocalStoragesessionStorageIndexedDB
容量4KB~5MB~5MB数百MB+
随请求发送✅ 自动❌❌❌
生命周期可设过期永久标签页关闭永久
API 类型同步同步同步异步
事务❌❌❌✅
存二进制❌❌❌✅

其实你每天都在用

  • 登录态:sessionId 在 HttpOnly Cookie 里,你从不手动传它,后端却每次都认得你
  • 暗色模式无闪烁:刷新前先从 localStorage 读 theme,设置 <html data-theme> 再渲染——否则闪白屏
  • 评论草稿救场:写了半小时的评论不小心关了标签页——sessionStorage 里的草稿帮你恢复(注意是 sessionStorage,关标签页才清)
  • Google Docs 离线编辑:所有文档内容存 IndexedDB,断网照写不误,联网后增量同步
  • PWA 秒开:Service Worker + CacheStorage 缓存整个应用的 HTML/JS/CSS,第二次打开不依赖网络

常见误解(FAQ)

  • ❌ 误区:「Token 存 localStorage 最方便,大家都这么干」 任何 XSS 注入的 <script> 一行 localStorage.getItem('token') 就能偷走。正确姿势:access token 存内存变量(刷新需重新登录),refresh token 走 HttpOnly + Secure + SameSite=Strict Cookie。方便不是安全的挡箭牌。

  • ❌ 误区:「SameSite=Strict 一定比 Lax 安全」 Strict 确实更严,但用户从邮件 / GitHub 点链接跳转到你的网站时 Cookie 不发送——永远是”未登录”状态。Lax 在安全与可用间取得平衡:允许安全的顶层 GET 导航携带 Cookie,阻止跨站 POST 等危险请求。

  • ❌ 误区:「sessionStorage 同源标签页就能共享」 绝对不能。这是它最核心的设计——每个标签页独立 session。两个标签页打开同一 URL,sessionStorage 完全隔离。跨标签页通信请用 BroadcastChannel 或 localStorage 的 storage 事件。

  • ❌ 误区:「数据量大就上 IndexedDB」 IndexedDB 的异步模型让代码复杂度陡增。5MB 以内用 localStorage + 过期封装完全够用。别为了用 IndexedDB 而用 IndexedDB——它解决的是”海量结构化数据 + 索引查询”这个特定问题。

一句话总结

选存储方案不是比谁容量大:Cookie 补 HTTP 无状态的坑,localStorage 存偏好,sessionStorage 做标签页隔离,IndexedDB 当本地数据库。面试官问”区别”,想听的是你有没有理解每种方案为什么被设计出来。

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