cookie、localStorage、sessionStorage、IndexedDB——四种浏览器端的存储方案。选哪个,取决于数据量大小、生命周期跨度和是否需要发给服务端。

cookie 最初是为「浏览器和服务器之间维持会话」而设计的,后来被借用做本地存储,但设计包袱一直没有卸掉。

// cookie 是追加式操作,不是 setter
document.cookie = 'a=100;'; // 写入 a=100
document.cookie = 'b=200;'; // 追加 b=200,不会覆盖 a
// 结果:'a=100; b=200'
 
// 同名 key 覆盖
document.cookie = 'a=999;'; // 覆盖 a=999
// 结果:'a=999; b=200'
 
// 设置过期时间(不设置则会话结束即过期)
document.cookie = 'token=abc; max-age=3600; path=/; Secure; SameSite=Strict';
特性说明
容量4KB(不同浏览器略有差异)
发送每次 HTTP 请求自动携带(同域),增加请求体积
作用域可设 pathdomain 控制范围,子域名可共享
安全属性HttpOnly(禁止 JS 读取)、Secure(仅 HTTPS)、SameSite(防 XSRF)
APIdocument.cookie——极其简陋,没有 get/set/remove 方法

cookie 在今天是身份认证令牌的最佳载体——配合 HttpOnly + Secure + SameSite=Strict,而不是数据存储工具。

SameSite 的三个值:Strict(完全禁止跨站,最安全)、Lax(默认值,允许顶级导航如点击链接时跨站携带)、None(允许跨站,但必须同时设置 Secure)。

localStorage 与 sessionStorage

HTML5 专门为本地存储设计了这两个 API,不随 HTTP 请求发送,接口简单:

// 统一的 API
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme'); // 'dark'
localStorage.removeItem('theme');
localStorage.clear(); // 清空当前域名的所有数据
 
// 也可以用对象属性语法(但不推荐,因为可能与内置属性冲突)
localStorage.theme = 'dark';
 
// sessionStorage 用法完全一样
sessionStorage.setItem('draft', 'hello');
维度localStoragesessionStorage
容量约 5MB(每个域名)约 5MB(每个域名)
生命周期永久存储,除非手动删除或代码清除标签页关闭即清除
作用域同源下所有标签页共享仅当前标签页(同源不同标签页互不共享
页面刷新保留保留
新标签页可访问不可访问
跨域 iframe遵循同源规则遵循同源规则

容易混淆的点:用 target="_blank"window.open 打开的新标签页,某些浏览器会复制当前页的 sessionStorage 到新页面,但这是实现细节,不要依赖。规范上 sessionStorage 是标签页级别的。

存储空间溢出

空间用尽时 setItemQuotaExceededError

try {
  localStorage.setItem('key', value);
} catch (e) {
  if (e.name === 'QuotaExceededError') {
    console.warn('localStorage 已满');
    // 处理策略:LRU 淘汰、提示用户清理、切换 IndexedDB
  }
}

只能存字符串

localStorage 和 sessionStorage 的键和值都只能是字符串:

// 存对象需要序列化
localStorage.setItem('user', JSON.stringify({ name: 'zhangsan' }));
const user = JSON.parse(localStorage.getItem('user'));
 
// 注意 JSON.stringify 的陷阱
JSON.stringify(undefined); // undefined —— 存入后取出来是字符串 'undefined'
JSON.stringify(NaN); // 'null' —— NaN 变 null
JSON.stringify(Infinity); // 'null' —— Infinity 变 null

三者对比

维度cookielocalStoragesessionStorage
容量~4KB~5MB~5MB
发送服务端✅ 每次请求
生命周期可设过期永久标签页关闭
作用域path+domain同源共享单标签页
API简洁简洁
跨标签页

选型思路

是否需要自动发送到服务端?
├── 是 → cookie(身份 token,配合 HttpOnly+Secure+SameSite)
└── 否:
    ├── 数据超过 5MB?→ IndexedDB
    ├── 需要结构化查询?→ IndexedDB
    └── 否:
        ├── 仅当前标签页有效?→ sessionStorage(表单草稿、临时状态)
        └── 否 → localStorage(主题、偏好、缓存数据)

IndexedDB

对于超过 5MB 的结构化数据、离线应用缓存、复杂查询需求,IndexedDB 是唯一选项。它是一个浏览器内建的非关系型数据库,支持:

  • 事务(transaction)
  • 索引(index)
  • 异步查询(不会阻塞主线程)
  • 存储 JavaScript 对象(包括 File、Blob)
// 打开数据库
const request = indexedDB.open('MyDB', 2);
 
request.onupgradeneeded = (e) => {
  const db = e.target.result;
  // 创建对象存储和索引
  const store = db.createObjectStore('users', { keyPath: 'id' });
  store.createIndex('name', 'name', { unique: false });
};
 
request.onsuccess = (e) => {
  const db = e.target.result;
  // 读写操作...
  const tx = db.transaction('users', 'readwrite');
  const store = tx.objectStore('users');
  store.add({ id: 1, name: 'zhangsan' });
};

原生 IndexedDB API 基于事件回调,比较繁琐。实际开发常用封装库:idb(轻量 Promise 封装)、Dexie.js(功能全面)、localForage(自动降级到 localStorage,适合简单 KV 场景)。

相关笔记