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

摘要 :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)

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 里别乱用。
相关推荐
小羊没烦恼!20 分钟前
T-SQL查询高级—SQL Server索引中的碎片和填充因子
开发语言·前端·javascript·数据库·后端·python·web安全
美味蛋炒饭.35 分钟前
java八股面试(2)
面试·职场和发展
CesareCheung37 分钟前
高级软件测试工程师面试锦集
面试·单元测试·压力测试
定栓1 小时前
Webpack迁到Vite第一节:模块系统迁移:require → import
运维·前端·webpack
小熊在忙fpga1 小时前
N32G430芯片的flash表示
前端·javascript·html
Data analyse4561 小时前
App埋点版本兼容的字段迁移校验
大数据·运维·服务器·前端·数据库·数据分析
todoitbo1 小时前
本来只想给内网开个口子,Natapp 却多给了一道防护
前端·javascript·vue.js·内网穿透·网络防护·natapp
会灭火的程序员1 小时前
SuperMap AgentX Server智能体接入WEB应用技术文档
前端·supermap·agentx·agentx server
CRMEB1 小时前
商品 SKU 设计,从简单规格到多规格踩过什么
java·前端·数据库·开源
Chloe.Zz1 小时前
一篇入门Agent
开发语言·前端·javascript