localStorage 五个坑:只存字符串、5MB 配额、隐私模式、跨标签页同步与过期策略

localStorage 只能存字符串:对象直接 setItem 进去会变成 '[object Object]'。此外还有 5MB 配额写满抛错、隐私模式不可用、storage 事件本页不触发、数据永不过期四个坑,本文一次讲完并给出带过期时间的读写封装。

约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

第 1 坑:localStorage 只能存字符串

setItem 会对值做隐式 String() 转换:对象进去出来就是 '[object Object]',数字 1 出来是 '1'。读写永远配 JSON.stringify / JSON.parse。

错误与正确姿势对比
// 错误:对象被隐式转成字符串
localStorage.setItem('user', { name: '张三' });
localStorage.getItem('user'); // '[object Object]'

// 正确:序列化写入,读取时兜底坏数据
function setJSON(key, value) {
  localStorage.setItem(key, JSON.stringify(value));
}
function getJSON(key, fallback = null) {
  try {
    const raw = localStorage.getItem(key);
    return raw === null ? fallback : JSON.parse(raw);
  } catch {
    return fallback; // 手动改坏或写入中断的脏数据
  }
}

剩下四个坑,一次列全

每一个都对应一类线上事故,逐条对号入座。

  1. 5MB 配额:写满抛 QuotaExceededError,一次 try/catch 能救页面流程;配额按 UTF-16 码元计,中文和 emoji 都翻倍占空间。
  2. 隐私模式:Safari 无痕窗口写入会抛错或配额为 0,首次写入前先探测,失败就静默降级为内存 Map。
  3. storage 事件只在「别的标签页」触发:本页自己 setItem 不会收到事件,想本页响应要自己封装 setter。
  4. 数据永不过期:localStorage 没有 TTL,一年前的缓存也一直占着配额,过期要自己实现(见下面的封装)。

localStorage / sessionStorage / Cookie 怎么选

关键差别在生命周期和是否随请求发送。

特性localStoragesessionStorageCookie
容量约 5MB约 5MB约 4KB
生命周期永久,手动清除标签页关闭即清空可设 Expires / Max-Age
随 HTTP 请求自动携带否否是(同源每次都带)
跨标签页共享是(同源)否是(按 domain/path)
易用性getItem/setItem 简单同左document.cookie 需自己封装

跨标签页同步:storage 事件

主题设置全标签页生效
// 本页 setItem 不触发本页的 storage 事件,只通知其他同源标签页
window.addEventListener('storage', (e) => {
  if (e.key === 'theme') {
    document.documentElement.dataset.theme = e.newValue ?? 'light';
  }
});

// 在另一个标签页执行:
localStorage.setItem('theme', 'dark');
// → 上面所有已打开标签页立即切到 dark

第 5 坑的解法:带过期时间的读写封装

把值和时间戳一起存,读取时判断是否超时。缓存类数据建议都走这个封装。

TTL 封装
function setWithTTL(key, value, ttlMs) {
  localStorage.setItem(key, JSON.stringify({ v: value, exp: Date.now() + ttlMs }));
}
function getWithTTL(key) {
  try {
    const item = JSON.parse(localStorage.getItem(key));
    if (!item || Date.now() > item.exp) {
      localStorage.removeItem(key); // 过期即清理,别占配额
      return null;
    }
    return item.v;
  } catch {
    return null;
  }
}

setWithTTL('cache:list', [{ id: 1, name: '张三' }], 5 * 60 * 1000); // 缓存 5 分钟

配额满了怎么办

QuotaExceededError 不是要不要处理的问题,而是必然会在某个用户机器上发生的问题。

  • 所有 setItem 包 try/catch,失败时降级:不缓存直接走接口,功能不中断。
  • 启动时顺手清理:遍历带 exp 的键删掉过期的,或按 key 前缀整批清除。
  • 几百 KB 以上的数据别放 localStorage,直接上 IndexedDB,容量按百 MB 计。
  • 编码也能省空间:JSON 里重复的长字段名可用短键映射,或存前先压缩(如 CompressionStream)。

安全红线

localStorage 对同源下所有 JS 完全透明,包括第三方脚本注入进来的。

  • 不存 JWT、密码、身份证号等敏感信息:XSS 一旦发生,localStorage 一读一个准。
  • 需要「自动登录」体验时,token 放 HttpOnly Cookie,前端拿不到反而更安全。
  • 存 token 前先解码看看 payload 里带了什么,敏感字段超预期就该换方案。
  • 键名加业务前缀(如 'pxbo:theme')避免同源多应用互相覆盖;上线前用无痕窗口把隐私模式路径过一遍。

常见问题

localStorage 和 sessionStorage 的区别是什么?

容量都约 5MB、API 相同。localStorage 永久保存且同源跨标签页共享;sessionStorage 随标签页关闭即清空,且每个标签页独立。

localStorage 里的数据会过期吗?

不会,不主动清除就一直在。需要过期就把时间戳和值一起存,读取时判断是否超时并 removeItem,十行代码即可封装。

localStorage 写满 5MB 报错怎么处理?

用 try/catch 捕获 QuotaExceededError 并降级:清理过期键、压缩数据,或把大块数据挪到 IndexedDB,别让一次写入打断页面流程。

把 JWT 存在 localStorage 安全吗?

不安全,页面里任何 XSS 脚本都能读走。优先用 HttpOnly Cookie;确实要存就配合严格的输入转义与 CSP,并缩短 token 有效期。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件