Cookie 对比:localStorage vs sessionStorage vs Cookie vs IndexedDB
Web 开发中有四种主要的客户端存储方案:Cookie、localStorage、sessionStorage 和 IndexedDB。它们各有优势和适用场景,选择正确的存储方案对应用性能和用户体验至关重要。本节将通过详细的对比表和实际场景分析,帮助你在不同需求下做出最佳选择。
前置知识
阅读本节前,建议先了解:IndexedDB 入门
基础概念
四种存储方案概览
| 存储方案 | 出现时期 | 本质 | 浏览器提供者 |
|---|---|---|---|
| Cookie | 1994 年 | HTTP 头部扩展 | Netscape |
| localStorage | 2010 年 (HTML5) | 键值对存储 | W3C |
| sessionStorage | 2010 年 (HTML5) | 会话键值对存储 | W3C |
| IndexedDB | 2015 年 | 客户端数据库 | W3C |
详细对比
核心特性对比
| 特性 | Cookie | localStorage | sessionStorage | IndexedDB |
|---|---|---|---|---|
| 存储容量 | 约 4KB | 约 5MB | 约 5MB | 数百 MB ~ 数 GB |
| 数据类型 | 字符串 | 字符串 | 字符串 | 对象、Blob、文件 |
| 生命周期 | 可设置过期时间 | 永久 | 会话(关闭标签页) | 永久 |
| 作用域 | 同源 + 路径 | 同源 | 同源 + 标签页 | 同源 |
| 随请求发送 | 是(每次 HTTP) | 否 | 否 | 否 |
| API 操作 | 同步 | 同步 | 同步 | 异步 |
| 索引/查询 | 无 | 无 | 无 | 支持索引和游标 |
| 安全性 | 可设置 HttpOnly | 无特殊保护 | 无特殊保护 | 无特殊保护 |
容量对比
Cookie: ████ 4 KB
localStorage: ██████████████████████ ... 5 MB
sessionStorage: ████████████████████ ... 5 MB
IndexedDB: ██████████████████████████████ ... 数百 MB ~ 数 GB浏览器兼容性
| 存储方案 | Chrome | Firefox | Safari | Edge | IE |
|---|---|---|---|---|---|
| Cookie | 1.0+ | 1.0+ | 1.0+ | 12+ | 3.0+ |
| localStorage | 4.0+ | 3.5+ | 4.0+ | 12+ | 8.0+ |
| sessionStorage | 5.0+ | 2.0+ | 4.0+ | 12+ | 8.0+ |
| IndexedDB | 24.0+ | 16.0+ | 10.1+ | 12+ | 10.0+ |
安全性对比
| 特性 | Cookie | localStorage | sessionStorage | IndexedDB |
|---|---|---|---|---|
| HttpOnly(防止 JS 读取) | 支持 | 不支持 | 不支持 | 不支持 |
| Secure(仅 HTTPS) | 支持 | N/A | N/A | N/A |
| SameSite(CSRF 防护) | 支持 | N/A | N/A | N/A |
| Path 限制 | 支持 | 不支持 | 不支持 | 不支持 |
| Domain 限制 | 支持 | 不支持 | 不支持 | 不支持 |
javascript
// Cookie 的安全属性
document.cookie = 'session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/';
// HttpOnly: JavaScript 无法读取(document.cookie 读不到)
// Secure: 仅在 HTTPS 下发送
// SameSite: 防止 CSRF 攻击
// Path: 限制作用路径详细说明
Cookie 的特点
优势:
- 每次 HTTP 请求自动携带,适合身份认证
- 支持过期时间控制
- 支持安全属性(HttpOnly、Secure、SameSite)
劣势:
- 容量极小(约 4KB)
- 每次请求都发送,影响性能
- API 设计古老,使用不便
javascript
// Cookie 基本操作
// 设置 Cookie
document.cookie = 'username=zhangsan; max-age=3600; path=/';
// 读取 Cookie(获取所有)
const cookies = document.cookie; // "username=zhangsan; theme=dark"
// 删除 Cookie(设置过期时间为过去)
document.cookie = 'username=; max-age=0; path=/';
// Cookie 封装工具
const CookieHelper = {
set(name, value, days = 7, path = '/') {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=${path}`;
},
get(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? decodeURIComponent(match[2]) : null;
},
delete(name, path = '/') {
document.cookie = `${name}=; max-age=0; path=${path}`;
}
};localStorage 的特点
优势:
- 容量较大(5MB)
- API 简单直观
- 数据不随请求发送
- 跨标签页共享
劣势:
- 只能存储字符串
- 同步操作可能阻塞
- 无索引查询能力
sessionStorage 的特点
优势:
- 自动清理,无需手动管理
- 标签页隔离,互不干扰
劣势:
- 刷新页面后保留,但关闭标签页后清除
- 不支持跨标签页通信
- 同样只能存储字符串
IndexedDB 的特点
优势:
- 容量极大(数百 MB 以上)
- 支持结构化数据和索引
- 异步操作不阻塞
- 支持事务和游标
劣势:
- API 复杂,使用门槛高
- 需要封装才能方便使用
- 数据操作都是异步的
场景选择指南
按场景推荐
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 用户登录 Token | Cookie (HttpOnly) | 自动携带、防 XSS |
| 用户主题偏好 | localStorage | 持久化、跨标签页 |
| 购物车数据 | localStorage | 持久化、中等数据量 |
| 表单临时数据 | sessionStorage | 关闭页面自动清除 |
| 一次性操作状态 | sessionStorage | 会话级别 |
| 离线应用数据 | IndexedDB | 大容量、结构化 |
| 搜索历史记录 | localStorage | 简单存储 |
| 实时通知计数 | Cookie 或 localStorage | 跨请求传递 |
| 大文件缓存 | IndexedDB | 支持二进制数据 |
| 复杂查询数据 | IndexedDB | 索引查询 |
| 多步表单数据 | sessionStorage | 会话内持久 |
| A/B 测试标记 | Cookie | 可在服务端读取 |
决策流程
需要存储数据
│
├── 数据需要每次请求发送到服务器?
│ └── 是 → Cookie
│
├── 数据需要存储超过 5MB?
│ └── 是 → IndexedDB
│
├── 数据需要在关闭标签页后保留?
│ ├── 是 → localStorage
│ └── 否 → sessionStorage
│
└── 数据需要复杂查询?
└── 是 → IndexedDB实战示例
存储方案选择器
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>存储方案对比</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; }
.comparison-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
font-size: 14px;
}
.comparison-table th, .comparison-table td {
padding: 12px 16px;
border: 1px solid #e0e0e0;
text-align: center;
}
.comparison-table th {
background: #1a73e8;
color: white;
font-weight: 600;
}
.comparison-table tr:nth-child(even) { background: #f8f9fa; }
.comparison-table td:first-child {
text-align: left;
font-weight: 600;
background: #e8f0fe;
}
.highlight { background: #e8f5e9 !important; }
.warning { background: #fff3cd !important; }
.demo-section {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.demo-btn {
padding: 8px 16px;
margin: 4px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
color: white;
}
.btn-cookie { background: #ff9800; }
.btn-local { background: #4CAF50; }
.btn-session { background: #2196F3; }
.btn-idb { background: #9c27b0; }
.result {
margin-top: 16px;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
font-family: monospace;
font-size: 13px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h2>客户端存储方案对比</h2>
<table class="comparison-table">
<thead>
<tr>
<th>特性</th>
<th>Cookie</th>
<th>localStorage</th>
<th>sessionStorage</th>
<th>IndexedDB</th>
</tr>
</thead>
<tbody>
<tr>
<td>容量</td>
<td class="warning">~4KB</td>
<td>~5MB</td>
<td>~5MB</td>
<td class="highlight">数百MB+</td>
</tr>
<tr>
<td>生命周期</td>
<td>可设置过期</td>
<td>永久</td>
<td>会话级</td>
<td>永久</td>
</tr>
<tr>
<td>随请求发送</td>
<td class="warning">是</td>
<td class="highlight">否</td>
<td class="highlight">否</td>
<td class="highlight">否</td>
</tr>
<tr>
<td>跨标签页</td>
<td>是</td>
<td>是</td>
<td>否</td>
<td>是</td>
</tr>
<tr>
<td>API 风格</td>
<td>字符串解析</td>
<td>简单键值</td>
<td>简单键值</td>
<td>数据库操作</td>
</tr>
<tr>
<td>异步</td>
<td>同步</td>
<td>同步</td>
<td>同步</td>
<td class="highlight">异步</td>
</tr>
<tr>
<td>索引查询</td>
<td>无</td>
<td>无</td>
<td>无</td>
<td class="highlight">支持</td>
</tr>
</tbody>
</table>
<div class="demo-section">
<h3>存储方案演示</h3>
<button class="demo-btn btn-cookie" onclick="testCookie()">测试 Cookie</button>
<button class="demo-btn btn-local" onclick="testLocalStorage()">测试 localStorage</button>
<button class="demo-btn btn-session" onclick="testSessionStorage()">测试 sessionStorage</button>
<button class="demo-btn btn-idb" onclick="testIndexedDB()">测试 IndexedDB</button>
<div class="result" id="result">点击按钮测试各存储方案</div>
</div>
<script>
const resultEl = document.getElementById('result');
// Cookie 测试
function testCookie() {
document.cookie = 'test=hello; max-age=60; path=/';
const cookies = document.cookie;
resultEl.textContent = `Cookie 测试:\n` +
`设置: document.cookie = 'test=hello'\n` +
`读取: document.cookie = "${cookies}"\n` +
`容量: ~4KB\n` +
`特点: 字符串格式,每次请求自动携带`;
}
// localStorage 测试
function testLocalStorage() {
localStorage.setItem('test', 'hello from localStorage');
const value = localStorage.getItem('test');
resultEl.textContent = `localStorage 测试:\n` +
`设置: localStorage.setItem('test', 'hello')\n` +
`读取: localStorage.getItem('test') = "${value}"\n` +
`容量: ~5MB\n` +
`特点: 永久存储,跨标签页共享`;
}
// sessionStorage 测试
function testSessionStorage() {
sessionStorage.setItem('test', 'hello from sessionStorage');
const value = sessionStorage.getItem('test');
resultEl.textContent = `sessionStorage 测试:\n` +
`设置: sessionStorage.setItem('test', 'hello')\n` +
`读取: sessionStorage.getItem('test') = "${value}"\n` +
`容量: ~5MB\n` +
`特点: 会话级,标签页隔离`;
}
// IndexedDB 测试
function testIndexedDB() {
const request = indexedDB.open('TestDB', 1);
request.onupgradeneeded = (e) => {
e.target.result.createObjectStore('items', { autoIncrement: true });
};
request.onsuccess = (e) => {
const db = e.target.result;
const tx = db.transaction('items', 'readwrite');
const store = tx.objectStore('items');
store.add({ message: 'hello from IndexedDB', time: Date.now() });
tx.oncomplete = () => {
const tx2 = db.transaction('items', 'readonly');
const req = tx2.objectStore('items').getAll();
req.onsuccess = () => {
resultEl.textContent = `IndexedDB 测试:\n` +
`数据库: TestDB\n` +
`数据: ${JSON.stringify(req.result, null, 2)}\n` +
`容量: 数百MB~数GB\n` +
`特点: 大容量,支持索引和事务`;
};
};
};
}
</script>
</body>
</html>注意事项
存储安全最佳实践
| 存储方案 | 安全建议 |
|---|---|
| Cookie | 使用 HttpOnly、Secure、SameSite 属性 |
| localStorage | 不存储敏感信息,防止 XSS 读取 |
| sessionStorage | 不存储敏感信息,数据生命周期短 |
| IndexedDB | 不存储密码、密钥等敏感数据 |
存储清理策略
javascript
// 定期清理过期数据
function cleanupStorage() {
// localStorage 清理
const now = Date.now();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('cache_')) {
try {
const item = JSON.parse(localStorage.getItem(key));
if (item.expiry && now > item.expiry) {
localStorage.removeItem(key);
console.log('已清理过期缓存:', key);
}
} catch { /* 忽略解析错误 */ }
}
}
}
// 每小时清理一次
setInterval(cleanupStorage, 3600000);最佳实践
- 认证信息:使用 Cookie + HttpOnly
- 用户偏好:使用 localStorage
- 临时数据:使用 sessionStorage
- 大量数据:使用 IndexedDB
- 混合使用:根据场景选择,不要只用一种方案
下一节
继续学习:Geolocation API 基础