#浏览器存储方案:localStorage、sessionStorage 与 Cookie

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


一、三种存储方案概述

1.1 什么是 Web Storage

localStoragesessionStorage 合称为 Web Storage,是 HTML5 引入的客户端存储 API。它们比 Cookie 更现代、更强大:

  • 容量大:通常 5MB ~ 10MB(因浏览器而异),Cookie 仅约 4KB
  • API 简洁setItem/getItem/removeItem/clear 四个方法搞定全部操作
  • 不自动发送服务器:数据纯本地使用,不会增加 HTTP 请求体积
  • 仅存字符串 :所有值都会被转为字符串存储(对象需 JSON.stringify

Cookie 是最早 的浏览器存储方案(1994 年由 Netscape 发明),最初设计目的是为了解决 HTTP 无状态 的会话保持问题。它的独特之处在于:每次 HTTP 请求都会自动携带,因此天然适合服务端读取。

1.3 一句话定位

方案 定位 一句话
Cookie 服务端通信载体 "每次请求我都跟着走"
localStorage 长期客户端仓库 "我不走,我就在这儿等你"
sessionStorage 临时标签页记事本 "关了我就没了"

二、核心区别详解

2.1 生命周期

方案 生命周期 说明
Cookie 可设置过期时间 设置了 expiresmax-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 里别乱用。
相关推荐
Dr.kangder1 小时前
嵌入式面试总结(二十一)——C语言关键字
c语言·开发语言·面试·职场和发展·架构·虚拟化
用户921080262861 小时前
1. Ant Design X Vue 项目介绍:结构、组件和启动方式
前端
摸鱼研究员1 小时前
neverthrow,ts 中优雅的异常处理方案
前端·javascript
jingchao19981 小时前
Cannot read properties of null (reading ‘insertBefore‘)
前端·javascript·vue.js
计算机魔术师1 小时前
新文章
前端
爱跳舞的烤冷面1 小时前
自学嵌入式第N天(Linux篇——文件编程)
java·开发语言·前端
IT_陈寒1 小时前
Vite的热更新突然失效,原来我忽略了这个配置
前端·人工智能·后端
怪奇云呼军1 小时前
闪电智能VoiceAgent 如何管理呼入、接听、桥接和挂断状态?
java·前端·网络·数据库·人工智能
云浪1 小时前
Milvus + RAG 实战:《红楼梦》问答助手
前端·人工智能·后端