2. localStorage 与 sessionStorage 的 API 封装

2.1 原理

Web Storage API 提供了两种浏览器端键值存储方案——localStorage 与 sessionStorage。两者共享相同的 setItem、getItem、removeItem、clear 接口,但在生命周期和场景上存在根本差异:

特性 localStorage sessionStorage
生命周期 永久存活,除非用户手动清除或调用 removeItem/clear 仅在当前标签页会话内有效,标签页关闭即清除
作用域 同源(协议+域名+端口)下所有页面共享 同源下同一标签页内共享,不同标签页隔离
存储容量 通常 5MB(不同浏览器略有浮动) 通常 5MB
数据类型 仅支持字符串(需手动序列化/反序列化) 同上

由于原生 API 只接受字符串值,直接存储对象、数组或数字会得到 [object Object] 等无效结果。因此需要封装 JSON 序列化/反序列化 层,同时必须处理 容量超限异常(QuotaExceededError) 和浏览器隐私模式下的写入失败。

2.2 实现流程

  1. 定义封装类,接收 storageType 参数决定使用 localStorage 或 sessionStorage。
  2. setItem(key, value):对 value 执行 JSON.stringify,调底层存储,捕获异常并回退处理。
  3. getItem(key):读取字符串,尝试 JSON.parse,若解析失败返回原始字符串或 null。
  4. removeItem(key) / clear():直接委托底层方法。
  5. 异常处理:对 setItem 做 try-catch,遇到 QuotaExceededError 时触发清理策略或提示用户。
  6. 扩展方法:提供 has(key)、length()、keys() 等便利方法。
Web Storage 封装架构 用户代码 StorageWrapper 封装类 JSON.stringify / parse localStorage sessionStorage 异常捕获 (try/catch) 支持自动 JSON 序列化、容量超限处理、 隐私模式兼容

2.3 API 封装实现

以下是一个健壮的 Web Storage 封装类,包含 JSON 序列化、容量异常处理和隐私模式兼容:

class StorageWrapper {
  constructor(storageType = 'localStorage') {
    // 优先使用传入类型,如果不可用降级到内存存储
    this.storage = (() => {
      try {
        const storage = window[storageType];
        const x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return storage;
      } catch (e) {
        // 隐私模式或不可用时使用内存存储
        const mem = new Map();
        return {
          getItem: (k) => mem.get(k) ?? null,
          setItem: (k, v) => mem.set(k, v),
          removeItem: (k) => mem.delete(k),
          clear: () => mem.clear(),
          get length() { return mem.size; },
          key: (i) => [...mem.keys()][i] ?? null
        };
      }
    })();
  }

  set(key, value) {
    try {
      const serialized = JSON.stringify(value);
      this.storage.setItem(key, serialized);
      return true;
    } catch (e) {
      if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
        console.warn('存储容量超限,尝试清理过期数据...');
        this._cleanExpired();
        // 重试一次
        try {
          this.storage.setItem(key, JSON.stringify(value));
          return true;
        } catch { /* 重试失败,抛出异常 */ }
      }
      console.error('Storage set 失败:', e);
      return false;
    }
  }

  get(key) {
    try {
      const raw = this.storage.getItem(key);
      if (raw === null) return null;
      return JSON.parse(raw);
    } catch {
      // 若解析失败,返回原始字符串
      return raw;
    }
  }

  remove(key) {
    this.storage.removeItem(key);
  }

  clear() {
    this.storage.clear();
  }

  has(key) {
    return this.storage.getItem(key) !== null;
  }

  // 简单过期清理策略(示例)
  _cleanExpired() {
    // 实际项目中可存储过期时间,此处仅清空半数最旧数据
    const keys = [];
    for (let i = 0; i < this.storage.length; i++) {
      keys.push(this.storage.key(i));
    }
    const toRemove = Math.floor(keys.length / 2);
    for (let i = 0; i < toRemove; i++) {
      this.storage.removeItem(keys[i]);
    }
  }
}

// 使用示例
const local = new StorageWrapper('localStorage');
const session = new StorageWrapper('sessionStorage');

local.set('user', { id: 1, name: '张三' });
console.log(local.get('user')); // { id: 1, name: '张三' }

2.4 调试方法

  • 浏览器 DevTools → Application → Local Storage / Session Storage:直接查看、修改、删除键值对。
  • 控制台快速测试:localStorage.setItem('test', 'hello'); 观察 Storage 面板是否刷新。
  • 利用封装类的 get 方法 检查反序列化结果是否符合预期。
  • 模拟容量超限:连续写入大字符串(例如 'x'.repeat(5 * 1024 * 1024))触发异常,测试封装类的容错逻辑。

2.5 部署注意事项

  • 所有 Web Storage 数据严格遵循同源策略,无法跨域共享。
  • 不要存储明文敏感信息(密码、、信用卡号),必要时加密后再存储。
  • 若应用离线场景依赖 localStorage,需检测隐私模式降级方案(如内存 Map)。
  • 清除缓存时需同步清除过期 localStorage 数据,避免冗余。

2.6 常见错误及规避方案

错误 原因 解决方案
存储对象显示 [object Object] 未使用 JSON.stringify 直接存储对象 封装类中强制序列化/反序列化
QuotaExceededError 超出 5MB 容量 try-catch 捕获,清理旧数据或提醒用户
隐私模式下 setItem 抛出异常 浏览器禁止写入 Storage 检测可用性并降级到内存 Map
反序列化失败返回 null 存储了非 JSON 格式的字符串 加 try-catch 返回原始字符串
跨标签页数据不同步 localStorage 会同步,但 sessionStorage 不会 根据场景选择存储类型;必要时监听 storage 事件

2.7 性能与稳定性优化

  • 批量读写:避免频繁单次 setItem,可将多个状态合并为一个对象一次性写入。
  • 惰性读取:封装类内部可增加缓存层,只在首次读取后缓存结果,减少 JSON.parse 调用。
  • 异常重试策略:容量超限时优先清理过期或最旧数据,再重试一次写入。
  • 监听 storage 事件:在同源其他标签页修改 localStorage 时,当前页能收到通知,可用于状态同步(仅 localStorage 触发)。
  • 避免存储大对象:单个键的值建议控制在 1MB 以内,防止序列化/反序列化耗时长、阻塞主线程。
最佳实践:在应用初始化时调用一次 StorageWrapper 的可用性检测并缓存存储实例,避免每次调用都检测隐私模式。设置全局 forKey 前缀组织命名空间,例如 app_settings_ 防止与其他库冲突。