讲解

浏览器提供了在本地保存数据的能力,最常用的是 localStorage:键值都是字符串,容量约 5MB,数据长期保存(关闭浏览器不丢),按「源」(协议+域名+端口)隔离。API 极简:localStorage.setItem('key', 'value')、getItem('key')(不存在返回 null)、removeItem('key')、clear()。它的孪生兄弟 sessionStorage 用法完全一样,区别只在生命周期:会话结束(标签页关闭)即清除。

localStorage 只能存字符串,存对象数组要自己序列化:setItem('user', JSON.stringify(user)),读出来再 JSON.parse。读的时候一定要考虑数据损坏或格式升级的可能——JSON.parse 包 try/catch,解析失败就当作没有数据,这是从 localStorage 读数据的防御性标准写法。

适用与不适用要分清:适合存用户偏好(主题、语言)、表单草稿、不敏感的标记位;不适合存敏感信息(localStorage 对页面所有脚本可见,XSS 一打就漏,token 等凭证优先放 HttpOnly Cookie)、不适合大体积结构化数据(那是 IndexedDB 的地盘)、也不适合频繁读写(API 是同步的,量大时阻塞主线程)。storage 事件还能监听其他标签页对同一存储的修改,用于多标签页同步。

示例

// 基础读写(键值都是字符串)
localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme')); // 'dark'
console.log(localStorage.getItem('not-exist')); // null

// 存对象:序列化进、解析出
const settings = { fontSize: 18, lang: 'zh' };
localStorage.setItem('settings', JSON.stringify(settings));

// 读取的防御性写法:解析失败当作没有
function loadSettings() {
  try {
    const raw = localStorage.getItem('settings');
    return raw ? JSON.parse(raw) : null;
  } catch {
    return null; // 数据损坏时优雅降级
  }
}
console.log(loadSettings());

// 删除与清空
localStorage.removeItem('theme');
// localStorage.clear(); // 清空本站所有键,慎用

// 记录草稿的典型场景
const input = document.querySelector('#editor');
input.value = localStorage.getItem('draft') ?? '';
input.addEventListener('input', () => {
  localStorage.setItem('draft', input.value);
});

// 多标签页同步:其他标签页修改时触发
window.addEventListener('storage', (event) => {
  console.log('键', event.key, '从', event.oldValue, '变成', event.newValue);
});

常见坑

  • 直接存对象:setItem('user', userObj) 存进去的是字符串 '[object Object]'。必须 JSON.stringify。
  • 读取不判空不防坏:getItem 返回 null 时 JSON.parse(null) 得到 null 还好,但存量脏数据会直接抛错——读取函数永远 try/catch。
  • 存敏感信息:localStorage 里的一切对页面脚本明文可见,XSS 漏洞会全盘拖走。凭证类信息交给 HttpOnly Cookie。
  • 隐私模式配额异常:部分浏览器隐私模式下 localStorage 不可用或配额为 0,setItem 会抛 QuotaExceededError,写入也要 try/catch 兜底。

小结

localStorage 按源隔离、长期保存、只存字符串;对象要 JSON 序列化,读取要防御性解析;敏感数据别放这里。下一章处理最常见的用户输入入口:表单。