摘要 :localStorage / sessionStorage(合称 Web Storage,HTML5 引入)和 Cookie 是浏览器端三种主流数据持久化方案。核心区别在于生命周期 (永久/会话/可设过期)、容量(~5MB vs ~4KB)、**是否随请求自动发送。选型口诀:需服务器交互用 Cookie,长期大容量客户端存 localStorage,临时标签页级数据用 sessionStorage。

一、三种存储方案概述
1.1 什么是 Web Storage
localStorage 和 sessionStorage 合称为 Web Storage,是 HTML5 引入的客户端存储 API。它们比 Cookie 更现代、更强大:
- 容量大:通常 5MB ~ 10MB(因浏览器而异),Cookie 仅约 4KB
- API 简洁 :
setItem/getItem/removeItem/clear四个方法搞定全部操作 - 不自动发送服务器:数据纯本地使用,不会增加 HTTP 请求体积
- 仅存字符串 :所有值都会被转为字符串存储(对象需
JSON.stringify)
1.2 什么是 Cookie
Cookie 是最早 的浏览器存储方案(1994 年由 Netscape 发明),最初设计目的是为了解决 HTTP 无状态 的会话保持问题。它的独特之处在于:每次 HTTP 请求都会自动携带,因此天然适合服务端读取。
1.3 一句话定位
| 方案 | 定位 | 一句话 |
|---|---|---|
| Cookie | 服务端通信载体 | "每次请求我都跟着走" |
| localStorage | 长期客户端仓库 | "我不走,我就在这儿等你" |
| sessionStorage | 临时标签页记事本 | "关了我就没了" |
二、核心区别详解
2.1 生命周期
| 方案 | 生命周期 | 说明 |
|---|---|---|
| Cookie | 可设置过期时间 | 设置了 expires 或 max-age 则到期删除;不设置则为会话 Cookie(关闭浏览器标签页即失效) |
| localStorage | 永久存储 | 除非用户手动清除(浏览器设置 → 清除数据)或代码调用 removeItem()/clear(),否则永不过期 |
| sessionStorage | 会话级存储 | 仅在当前浏览器标签页(tab)的会话期间有效;关闭标签页或浏览器后数据即清除 |
javascript
// === Cookie 过期时间设置 ===
// 服务端写 Cookie 时通过 Set-Cookie 头指定过期时间:
// "token=abc123; expires=Thu, 01 Jan 2027 00:00:00 GMT"
// 客户端写 Cookie(document.cookie):
document.cookie = "theme=dark; max-age=3600"; // 1 小时后过期
document.cookie = "session=xyz;"; // 会话 Cookie,关闭标签页失效
// === localStorage 永不过期 ===
localStorage.setItem('name', 'heyi'); // 写入后永久存在
// 只能手动清除:
localStorage.removeItem('name'); // 删除单个
localStorage.clear(); // 清空全部
// === sessionStorage 标签页关闭即消失 ===
sessionStorage.setItem('tempData', 'step1');
// 关闭此标签页后 tempData 自动清除
2.2 存储容量
| 方案 | 容量 | 注意事项 |
|---|---|---|
| Cookie | 非常小 ,通常每个域名下约 4KB(含键名=值+属性) | 超出会被静默截断或丢弃 |
| localStorage | 较大 ,通常 5MB ~ 10MB(因浏览器而异) | 超出会抛 QuotaExceededError |
| sessionStorage | 与 localStorage 相同,通常 5MB ~ 10MB | 同上 |
javascript
// === 容量测试 ===
try {
// 尝试写入超大数据测试 localStorage 上限
const bigData = 'x'.repeat(1024 * 1024 * 10); // 10MB
localStorage.setItem('test', bigData);
} catch (e) {
console.error('存储空间不足:', e.name); // QuotaExceededError
}
// === 查看已用空间 ===
function getStorageUsage() {
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
total += localStorage.getItem(key).length * 2; // UTF-16 = 2 bytes/char
}
}
return `${(total / 1024).toFixed(2)} KB`;
}
console.log('localStorage 已用:', getStorageUsage());
2.3 与服务器的通信方式(关键差异!)
| 方案 | 是否自动发送到服务器 | 说明 |
|---|---|---|
| Cookie | ✅ 自动发送 | 每次 HTTP 请求(同域)的请求头中都会自动附带 Cookie: 字段 |
| localStorage | ❌ 不会自动发送 | 数据纯本地,需要时由 JS 手动读取并通过 AJAX/Fetch 发送 |
| sessionStorage | ❌ 不会自动发送 | 同 localStorage |
这是三者最本质的区别,直接影响选型:
javascript
// === Cookie 自动跟随请求 ===
// 假设服务端设置了 Cookie: token=abc123
// 之后的每一次 fetch / XHR 都会自动带上:
fetch('/api/user');
// 请求头自动包含:
// Cookie: token=abc123 ← 无需手动处理!
// === Storage 需要手动发送 ===
const token = localStorage.getItem('token');
fetch('/api/user', {
headers: { 'Authorization': `Bearer ${token}` } // 必须手动取出来放进去
});
性能影响 :如果 Cookie 存了大量数据,每个请求都会变重。这也是为什么 JWT Token 通常放在 localStorage 而非 Cookie 的原因之一(除非你有严格的 CSRF 防护需求)。
2.4 作用域
| 方案 | 作用域 | 细节 |
|---|---|---|
| Cookie | 同源下所有标签页和窗口共享 | 可通过 domain 属性扩展到子域名(如 .example.com) |
| localStorage | 同源(协议+域名+端口)下所有标签页和窗口共享 | 不同标签页可以互相读写 |
| sessionStorage | 仅在当前标签页(top-level browsing context)内有效 | 同源下不同标签页各自独立,互不相通;但同一标签页的 iframe 可以共享 |
javascript
// === 作用域演示 ===
// 在标签页 A 中写入:
localStorage.setItem('shared', 'all-tabs-can-read');
sessionStorage.setItem('private', 'only-this-tab');
// 在标签页 B(同域)中读取:
console.log(localStorage.getItem('shared')); // ✅ 'all-tabs-can-read'
console.log(sessionStorage.getItem('private')); // ❌ null(不同标签页)
// === Cookie 跨子域设置 ===
// 服务端设置 domain 为父域名即可实现子域共享:
// Set-Cookie: session_id=xxx; domain=.example.com; path=/
// 这样 a.example.com 和 b.example.com 都能读到这个 Cookie
2.5 API 易用性
| 方案 | API 风格 | 易用程度 |
|---|---|---|
| Cookie | document.cookie 字符串读写,需手动拼接格式 |
⚠️ 笨拙,需自己处理编码/解析 |
| localStorage | setItem/getItem/removeItem/clear/key/length |
✅ 简洁直观 |
| sessionStorage | API 与 localStorage 完全相同 | ✅ 简洁直观 |
javascript
// ========== Cookie 原生 API(笨拙)==========
// 写入------需要手动拼接格式字符串:
document.cookie = "username=heyi; path=/; max-age=86400; SameSite=Lax";
// 读取------返回的是分号分隔的全部 Cookie 字符串,需自行解析:
function getCookie(name) {
const match = document.cookie.match(
new RegExp('(^| )' + name + '=([^;]+)')
);
return match ? decodeURIComponent(match[2]) : null;
}
console.log(getCookie('username')); // 'heyi'
// 删除------设置 max-age=0 或过期时间为过去:
document.cookie = "username=; max-age=0; path=/";
// ========== Web Storage API(简洁)==========
// 写入
localStorage.setItem('name', 'heyi');
localStorage.setItem('age', 18);
localStorage.setItem('gender', 'male');
// 读取
console.log(localStorage.getItem('name')); // 'heyi'
// 删除
localStorage.removeItem('name');
// 清空全部
localStorage.clear();
// 其他实用方法
console.log(localStorage.length); // 当前存储的键值对数量
console.log(localStorage.key(0)); // 获取索引为 0 的键名
for (let i = 0; i < localStorage.length; i++) {
console.log(localStorage.key(i), ':', localStorage.getItem(localStorage.key(i)));
}
// sessionStorage 的 API 与 localStorage 完全一致:
sessionStorage.setItem('temp', 'value');
sessionStorage.getItem('temp');
三、实战代码示例
3.1 localStorage 完整 CRUD 操作
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><title>localStorage 演示</title></head>
<body>
<script>
// ======== 1. 存储(Create)========
localStorage.setItem('name', 'heyi');
localStorage.setItem('age', 18);
localStorage.setItem('gender', 'male');
// ======== 2. 读取(Read)==========
console.log(localStorage.getItem('name')); // 'heyi'
console.log(localStorage.getItem('age')); // '18'(注意:是字符串!)
// ======== 3. 删除(Delete)==========
localStorage.removeItem('name');
// ======== 4. 清空全部 ==========
localStorage.clear();
</script>
</body>
</html>
3.2 存储对象与复杂数据(JSON 序列化)
Web Storage 只能存储字符串,复杂类型必须序列化:
javascript
// ======== 存储对象(JSON.stringify)==========
localStorage.setItem(
'token',
JSON.stringify({
data: "123456",
expires: Date.now() + 1000 * 60 * 60 * 24 // 24小时后过期
})
);
// ======== 读取并反序列化(JSON.parse)==========
function readToken() {
const raw = localStorage.getItem("token");
if (!raw) return null;
const token = JSON.parse(raw);
console.log(token.data); // "123456"
console.log(token.expires); // 时间戳
// ======== 手动实现过期检查 ==========
if (token.expires < Date.now()) {
console.log("token 已过期");
localStorage.removeItem("token"); // 清除过期数据
return null;
}
return token;
}
// 使用
const currentToken = readToken();
if (!currentToken) {
// token 过期或不存在,跳转登录
window.location.href = '/login';
}
注意 :localStorage 本身没有内置过期机制 ,上面的
expires字段是我们手动模拟的。如果需要更完善的 TTL(Time To Live),建议封装一个工具类(见下方 3.4)。
3.3 服务端写 Cookie(Node.js 示例)
Cookie 可以由服务端 通过 HTTP 响应头 Set-Cookie 写入,也可以由客户端 通过 document.cookie 写入:
javascript
// server.js --- Node.js 服务端写 Cookie
const http = require("http");
const app = http.createServer((req, res) => {
// 通过 Set-Cookie 响应头写入 Cookie
res.setHeader(
"Set-Cookie",
"token=0000; expires=Thu, 01 Jan 2025 00:00:00 GMT; Path=/; HttpOnly; SameSite=Strict"
);
res.end("hello cookie");
});
app.listen(3000, () => {
console.log("server is running at http://localhost:3000");
});
Set-Cookie 关键属性说明:
| 属性 | 作用 | 推荐设置 |
|---|---|---|
expires / max-age |
过期时间 | 敏感数据设短过期时间 |
Path |
生效路径 | 通常设 /(全站生效) |
Domain |
生效域名 | 默认当前域名;设 .example.com 可跨子域 |
HttpOnly |
禁止 JS 读取 | 强烈推荐防 XSS 窃取 |
Secure |
仅 HTTPS 传输 | 生产环境开启 |
SameSite |
跨站请求控制 | Strict(严格)/ Lax(宽松,允许导航)/ None(全放行) |
3.4 封装一个带 TTL 的 Storage 工具类
javascript
/**
* 带 TTL(过期时间)的 localStorage 工具类
* 解决原生 localStorage 无法自动过期的痛点
*/
class TTLStorage {
constructor(prefix = 'app') {
this.prefix = prefix;
}
_key(key) { return `${this.prefix}:${key}`; }
set(key, value, ttlMs) {
const item = {
value,
expires: ttlMs ? Date.now() + ttlMs : null, // null = 永不过期
createdAt: Date.now()
};
localStorage.setItem(this._key(key), JSON.stringify(item));
}
get(key) {
const raw = localStorage.getItem(this._key(key));
if (!raw) return null;
const item = JSON.parse(raw);
// 检查是否过期
if (item.expires && item.expires < Date.now()) {
localStorage.removeItem(this._key(key));
return null;
}
return item.value;
}
remove(key) {
localStorage.removeItem(this._key(key));
}
clear() {
// 只清除带前缀的项,不影响其他数据
Object.keys(localStorage)
.filter(k => k.startsWith(this.prefix))
.forEach(k => localStorage.removeItem(k));
}
}
// 使用示例
const store = new TTLStorage('myapp');
store.set('user', { name: 'heyi', role: 'admin' }, 1000 * 60 * 30); // 30分钟过期
store.set('theme', 'dark'); // 永不过期
const user = store.get('user'); // 30分钟内有效,过期返回 null
四、全景对比表
4.1 核心维度对比
| 特性 | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| 生命周期 | 可设过期时间;不设则随会话结束(关闭标签页) | 永久存储,除非手动清除或代码删除 | 会话级存储,关闭标签页即清除 |
| 存储容量 | 约 4KB(每个 Cookie) | 约 5MB ~ 10MB | 约 5MB ~ 10MB |
| 与服务器通信 | ✅ 自动发送,每次 HTTP 请求都携带 | ❌ 纯本地,不自动发送 | ❌ 纯本地,不自动发送 |
| 作用域 | 同源所有标签页+窗口;可通过 domain 跨子域 |
同源所有标签页+窗口共享 | 仅当前标签页有效;不同标签页互不相通 |
| API 易用性 | ⚠️ 原生 API 笨拙(字符串拼接/解析) | ✅ 简洁(setItem/getItem/removeItem/clear) | ✅ 与 localStorage 完全相同 |
| 数据类型 | 仅字符串 | 仅字符串(自动 toString) | 仅字符串(自动 toString) |
| 安全性 | 可设 HttpOnly 防 XSS 读取 | JS 可直接读取(XSS 风险) | JS 可直接读取(XSS 风险) |
| 索引/遍历 | 需自行解析 document.cookie | 支持 length/key(n) 遍历 |
支持 length/key(n) 遍历 |
| 同标签页多 iframe | 共享 | 共享 | 同一标签页内的 iframe 共享 |
| 引入版本 | HTML 起就有(1994) | HTML5(2009) | HTML5(2009) |
4.2 选型决策速查
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 身份认证 Token / Session ID | Cookie(HttpOnly) | 自动携带 + HttpOnly 防 XSS |
| 用户偏好设置(主题/语言) | localStorage | 长期保存 + 大容量 + 不占用请求带宽 |
| 多步表单中间状态 | sessionStorage | 标签页关闭即清理,不留垃圾 |
| 应用离线缓存 | localStorage | 永久存储 + 容量够用 |
| 购物车数据 | localStorage | 跨标签页共享 + 持久化 |
| 临时页面状态(滚动位置等) | sessionStorage | 临时性质 + 隔离性好 |
| A/B Test 分桶信息 | Cookie | 需要服务端读取 |
| CSRF Token | Cookie(HttpOnly+SameSite) | 安全要求高 + 自动携带 |
五、常见陷阱与最佳实践
5.1 五大陷阱
陷阱 1:localStorage 只能存字符串
javascript
// ❌ 错误:对象会被自动转成 "[object Object]"
localStorage.setItem('user', { name: 'heyi' });
localStorage.getItem('user'); // "[object Object]"
// ✅ 正确:先序列化
localStorage.setItem('user', JSON.stringify({ name: 'heyi' }));
JSON.parse(localStorage.getItem('user')); // { name: 'heyi' }
陷阱 2:localStorage 没有内置过期机制
javascript
// ❌ 直接存 token 不过期,永远留在用户浏览器里
localStorage.setItem('token', 'jwt_xxx');
// ✅ 封装 TTL 或定期检查(见上方 TTLStorage 类)
陷阱 3:Cookie 的 4KB 限制容易被忽略
javascript
// ❌ 把大量用户信息塞进 Cookie
document.cookie = `userInfo=${JSON.stringify(bigUserData)};`;
// 可能被截断导致数据损坏!
// ✅ 大数据放 localStorage,Cookie 只放关键 ID
localStorage.setItem('userInfo', JSON.stringify(bigUserData));
document.cookie = `userId=${bigUserData.id};`; // 只有几十字节
陷阱 4:storage 事件跨标签页通信
javascript
// 当 localStorage/sessionStorage 被修改时,
// **其他**同源标签页会收到 storage 事件(当前修改的标签页收不到)
window.addEventListener('storage', (e) => {
console.log(`键 ${e.key} 被改变了`);
console.log(`旧值: ${e.oldValue}`);
console.log(`新值: ${e.newValue}`);
console.log(`来源页面: ${e.url}`);
// 典型应用:多标签页同步登出
if (e.key === 'token' && e.newValue === null) {
alert('您已在其他标签页退出登录');
location.reload();
}
});
陷阱 5:隐私模式 / 无痕浏览的影响
| 浏览器 | 隐私模式下 localStorage | 隐私模式下 Cookie |
|---|---|---|
| Chrome | 内存级,关闭窗口即清空 | 内存级,关闭即清空 |
| Safari | 完全禁用!写入静默失败 | 会话级 Cookie |
| Firefox | 正常工作 | 正常工作 |
javascript
// 兼容性检测
function isLocalStorageAvailable() {
try {
const test = '__storage_test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false; // Safari 隐私模式等场景
}
}
5.2 安全最佳实践
javascript
// ========== Cookie 安全配置模板 ==========
// 服务端设置 Cookie 时的安全三件套:
const secureCookieOptions = [
'HttpOnly', // ① JS 读不到,防 XSS 窃取
'Secure', // ② 仅 HTTPS 传输,防中间人窃听
'SameSite=Strict' // ③ 禁止跨站携带,防 CSRF 攻击
].join('; ');
// Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=Strict
// ========== localStorage 安全注意事项 ==========
// 1. 不要存敏感信息(密码/身份证号/token 明文)
// 2. 如果必须存 token,确保站点有 CSP 防护 XSS
// 3. 敏感操作做二次验证(如支付时重新输入密码)
六、面试高频 Q&A
Q1:一句话说出三者的最大区别?
Cookie 是"跟服务器走的信使",localStorage 是"本地保险箱",sessionStorage 是"便利贴"。 Cookie 每次请求自动携带(适合身份认证),localStorage 永久大容量纯本地(适合偏好设置),sessionStorage 标签页关闭即消失(适合临时状态)。
Q2:为什么 JWT Token 有人放 Cookie,有人放 localStorage?
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Cookie(HttpOnly) | 防 XSS 窃取、自动携带 | 有 CSRF 风险、受 4KB 限制 | 传统 session 认证 |
| localStorage | 容量大、灵活、不受 CSRF 影响 | XSS 可窃取、需手动附加 header | 现代 SPA + JWT |
| 混合方案 | 兼顾两者优势 | 实现稍复杂 | 高安全要求的系统 |
最佳实践 :Token 放 Cookie 并加 HttpOnly + SameSite + CSRF Token 双重防护,或者放 localStorage 但严格防范 XSS(CSP + 输入过滤)。
Q3:localStorage 和 sessionStorage 能否跨域访问?
不能。 两者都遵循同源策略 (协议 + 域名 + 端口完全一致)。不同域名之间无法互相访问对方的 Storage。跨域通信需要用 postMessage 或 SharedWorker。
Q4:如何监听 Storage 变化?
使用 storage 事件,但要注意:触发变化的那个标签页本身不会收到事件 ,只有其他同源标签页才会收到。这使其非常适合多标签页数据同步(如一处登出、处处登出)。
Q5:Cookie 的 SameSite 属性有什么作用?
控制 Cookie 在跨站请求时是否发送:
- Strict:完全不发送(最安全,影响用户体验)
- Lax :允许顶级导航(如
<a>链接跳转)携带,禁止第三方网站请求携带(推荐默认值) - None :全部允许(必须配合
Secure使用)
七、记忆口诀
bash
Cookie 随请求自动走,四 KB 小巧适合认证;
local 永久五 MB 大,偏好设置离线缓存;
session 关标即消失,表单临时状态用它;
Storage 只能存字符串,对象记得 JSON 化;
过期机制得自己写,TTL 封装不要忘;
HttpOnly 防 XSS,SameSite 防 CSRF;
隐私模式有坑点,Safari 里别乱用。