约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
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; // 手动改坏或写入中断的脏数据
}
}每一个都对应一类线上事故,逐条对号入座。
关键差别在生命周期和是否随请求发送。
| 特性 | localStorage | sessionStorage | Cookie |
|---|---|---|---|
| 容量 | 约 5MB | 约 5MB | 约 4KB |
| 生命周期 | 永久,手动清除 | 标签页关闭即清空 | 可设 Expires / Max-Age |
| 随 HTTP 请求自动携带 | 否 | 否 | 是(同源每次都带) |
| 跨标签页共享 | 是(同源) | 否 | 是(按 domain/path) |
| 易用性 | getItem/setItem 简单 | 同左 | document.cookie 需自己封装 |
// 本页 setItem 不触发本页的 storage 事件,只通知其他同源标签页
window.addEventListener('storage', (e) => {
if (e.key === 'theme') {
document.documentElement.dataset.theme = e.newValue ?? 'light';
}
});
// 在另一个标签页执行:
localStorage.setItem('theme', 'dark');
// → 上面所有已打开标签页立即切到 dark把值和时间戳一起存,读取时判断是否超时。缓存类数据建议都走这个封装。
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 不是要不要处理的问题,而是必然会在某个用户机器上发生的问题。
localStorage 对同源下所有 JS 完全透明,包括第三方脚本注入进来的。
容量都约 5MB、API 相同。localStorage 永久保存且同源跨标签页共享;sessionStorage 随标签页关闭即清空,且每个标签页独立。
不会,不主动清除就一直在。需要过期就把时间戳和值一起存,读取时判断是否超时并 removeItem,十行代码即可封装。
用 try/catch 捕获 QuotaExceededError 并降级:清理过期键、压缩数据,或把大块数据挪到 IndexedDB,别让一次写入打断页面流程。
不安全,页面里任何 XSS 脚本都能读走。优先用 HttpOnly Cookie;确实要存就配合严格的输入转义与 CSP,并缩短 token 有效期。