一、Web Storage 基础
1. localStorage 与 sessionStorage 对比
特性 | localStorage | sessionStorage |
---|---|---|
生命周期 | 永久存储,除非手动清除 | 会话级别,标签页关闭即清除 |
作用域 | 同源的所有标签页共享 | 仅当前标签页可用 |
存储容量 | 通常 5MB 左右 | 通常 5MB 左右 |
数据格式 | 仅支持字符串 | 仅支持字符串 |
API 访问 | window.localStorage | window.sessionStorage |
2. 基本 API 使用
javascript
// 存储数据
localStorage.setItem('key', 'value');
sessionStorage.setItem('sessionKey', 'sessionValue');
// 读取数据
const value = localStorage.getItem('key');
const sessionValue = sessionStorage.getItem('sessionKey');
// 删除数据
localStorage.removeItem('key');
sessionStorage.removeItem('sessionKey');
// 清空所有数据
localStorage.clear();
sessionStorage.clear();
// 获取键名
const keyName = localStorage.key(0); // 获取第一个键名
二、实际项目封装实践
在实际项目中,我们通常会对 Web Storage 进行封装,主要解决以下问题:
- 自动序列化和反序列化 JSON 数据
- 统一错误处理
- 添加类型安全
- 设置命名空间防止键名冲突
- 添加过期时间功能
1. 基础封装实现
typescript
class WebStorage {
constructor(private storage: Storage) {}
// 设置存储项
set<T>(key: string, value: T): void {
try {
const serialized = JSON.stringify(value);
this.storage.setItem(key, serialized);
} catch (error) {
console.error(`Storage set error for key "${key}":`, error);
}
}
// 获取存储项
get<T>(key: string, defaultValue: T | null = null): T | null {
try {
const serialized = this.storage.getItem(key);
return serialized ? JSON.parse(serialized) : defaultValue;
} catch (error) {
console.error(`Storage get error for key "${key}":`, error);
return defaultValue;
}
}
// 移除存储项
remove(key: string): void {
this.storage.removeItem(key);
}
// 清空存储
clear(): void {
this.storage.clear();
}
}
// 创建实例
export const localStore = new WebStorage(localStorage);
export const sessionStore = new WebStorage(sessionStorage);
2. 带命名空间和过期时间的增强封装
typescript
interface StorageItem<T> {
value: T;
expires?: number; // 过期时间戳
}
class AdvancedStorage {
constructor(
private storage: Storage,
private namespace: string
) {}
private getKey(key: string): string {
return `${this.namespace}:${key}`;
}
set<T>(key: string, value: T, ttl?: number): void {
try {
const storageKey = this.getKey(key);
const item: StorageItem<T> = {
value,
expires: ttl ? Date.now() + ttl * 1000 : undefined
};
this.storage.setItem(storageKey, JSON.stringify(item));
} catch (error) {
console.error(`Storage set error:`, error);
}
}
get<T>(key: string, defaultValue: T | null = null): T | null {
try {
const storageKey = this.getKey(key);
const itemStr = this.storage.getItem(storageKey);
if (!itemStr) return defaultValue;
const item = JSON.parse(itemStr) as StorageItem<T>;
// 检查是否过期
if (item.expires && Date.now() > item.expires) {
this.remove(key);
return defaultValue;
}
return item.value;
} catch (error) {
console.error(`Storage get error:`, error);
return defaultValue;
}
}
remove(key: string): void {
this.storage.removeItem(this.getKey(key));
}
clear(): void {
Object.keys(this.storage)
.filter(key => key.startsWith(`${this.namespace}:`))
.forEach(key => this.storage.removeItem(key));
}
}
// 使用示例
export const authStorage = new AdvancedStorage(localStorage, 'auth');
export const tempStorage = new AdvancedStorage(sessionStorage, 'temp');
三、实际项目应用场景
1. 用户认证信息存储
typescript
// 存储用户token (7天有效期)
authStorage.set('token', {
accessToken: 'xxxxx',
refreshToken: 'yyyyy'
}, 60 * 60 * 24 * 7);
// 获取token
const token = authStorage.get<{
accessToken: string;
refreshToken: string;
}>('token');
if (token) {
// 使用token发起请求
api.setAuthorization(token.accessToken);
}
2. 表单草稿保存
typescript
// 保存表单草稿
function saveDraft(formData: FormData) {
sessionStore.set('formDraft', formData);
}
// 页面加载时恢复草稿
window.addEventListener('load', () => {
const draft = sessionStore.get<FormData>('formDraft');
if (draft) {
restoreForm(draft);
}
});
// 表单提交成功后清除草稿
function onSubmitSuccess() {
sessionStore.remove('formDraft');
}
3. 应用偏好设置
typescript
// 用户主题偏好
const themeStore = new AdvancedStorage(localStorage, 'preference');
// 设置主题
function setTheme(theme: 'light' | 'dark') {
themeStore.set('theme', theme);
applyTheme(theme);
}
// 初始化时应用保存的主题
const savedTheme = themeStore.get<'light' | 'dark'>('theme', 'light');
applyTheme(savedTheme);
4. 购物车临时存储
typescript
const cartStorage = new AdvancedStorage(sessionStorage, 'cart');
// 添加商品到购物车
function addToCart(product: Product, quantity: number) {
const currentCart = cartStorage.get<CartItem[]>('items') || [];
const existingItem = currentCart.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
currentCart.push({
id: product.id,
name: product.name,
price: product.price,
quantity
});
}
cartStorage.set('items', currentCart);
}
// 结账后清空购物车
function checkout() {
// ...结账逻辑
cartStorage.remove('items');
}
四、高级应用与优化
1. 存储加密
对于敏感信息,可以添加简单的加密:
typescript
import CryptoJS from 'crypto-js';
const SECRET_KEY = 'your-secret-key';
class SecureStorage extends AdvancedStorage {
private encrypt(data: string): string {
return CryptoJS.AES.encrypt(data, SECRET_KEY).toString();
}
private decrypt(cipherText: string): string {
const bytes = CryptoJS.AES.decrypt(cipherText, SECRET_KEY);
return bytes.toString(CryptoJS.enc.Utf8);
}
set<T>(key: string, value: T, ttl?: number): void {
const item: StorageItem<T> = { value, expires: ttl ? Date.now() + ttl * 1000 : undefined };
super.set(key, this.encrypt(JSON.stringify(item)));
}
get<T>(key: string, defaultValue: T | null = null): T | null {
const encrypted = super.get<string>(key);
if (!encrypted) return defaultValue;
try {
const decrypted = this.decrypt(encrypted);
const item = JSON.parse(decrypted) as StorageItem<T>;
if (item.expires && Date.now() > item.expires) {
this.remove(key);
return defaultValue;
}
return item.value;
} catch {
return defaultValue;
}
}
}
2. 存储事件监听
可以监听 storage 事件实现跨标签页通信:
typescript
// 封装存储事件监听
class StorageEventManager {
private listeners: Record<string, Function[]> = {};
constructor() {
window.addEventListener('storage', this.handleStorageEvent);
}
private handleStorageEvent = (event: StorageEvent) => {
if (!event.key || !this.listeners[event.key]) return;
this.listeners[event.key].forEach(callback => {
try {
const newValue = event.newValue ? JSON.parse(event.newValue) : null;
const oldValue = event.oldValue ? JSON.parse(event.oldValue) : null;
callback(newValue, oldValue, event);
} catch (error) {
console.error('Storage event parse error:', error);
}
});
};
addListener(key: string, callback: Function) {
if (!this.listeners[key]) {
this.listeners[key] = [];
}
this.listeners[key].push(callback);
}
removeListener(key: string, callback: Function) {
if (!this.listeners[key]) return;
this.listeners[key] = this.listeners[key].filter(fn => fn !== callback);
}
}
// 使用示例
export const storageEvent = new StorageEventManager();
// 在标签页A
authStorage.set('user', { name: 'Alice' });
// 在标签页B
storageEvent.addListener('auth:user', (newUser) => {
console.log('User data updated:', newUser);
});
3. 存储容量检测
typescript
function testStorageQuota(storage: Storage): Promise<number> {
return new Promise((resolve) => {
const key = 'quota-test';
let total = 0;
const value = new Array(1024).fill('a').join(''); // 1KB
const trySet = () => {
try {
storage.setItem(key, value + value);
total += value.length;
setTimeout(trySet, 0);
} catch (e) {
storage.removeItem(key);
resolve(total);
}
};
trySet();
});
}
// 使用
testStorageQuota(localStorage).then(bytes => {
console.log(`Available storage: ${(bytes / 1024 / 1024).toFixed(2)} MB`);
});
五、最佳实践
-
合理选择存储类型:
- 长期偏好设置 → localStorage
- 会话临时数据 → sessionStorage
-
敏感信息处理:
- 避免存储密码、信用卡号等敏感信息
- 必要时加密存储
-
数据清理策略:
- 设置合理的过期时间
- 提供手动清理方法
- 应用启动时清理过期数据
-
错误处理:
- 捕获可能的存储错误(如超出配额)
- 提供降级方案
-
性能考虑:
- 避免存储大型数据(超过1MB)
- 复杂数据考虑分块存储
-
类型安全:
- 使用TypeScript确保类型安全
- 为存储的数据定义清晰接口