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 实现流程
- 定义封装类,接收
storageType参数决定使用 localStorage 或 sessionStorage。 - setItem(key, value):对 value 执行
JSON.stringify,调底层存储,捕获异常并回退处理。 - getItem(key):读取字符串,尝试
JSON.parse,若解析失败返回原始字符串或 null。 - removeItem(key) / clear():直接委托底层方法。
- 异常处理:对
setItem做 try-catch,遇到QuotaExceededError时触发清理策略或提示用户。 - 扩展方法:提供
has(key)、length()、keys()等便利方法。
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_ 防止与其他库冲突。