Web Storage:localStorage 与 sessionStorage

一、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`);
});

五、最佳实践

  1. 合理选择存储类型

    • 长期偏好设置 → localStorage
    • 会话临时数据 → sessionStorage
  2. 敏感信息处理

    • 避免存储密码、信用卡号等敏感信息
    • 必要时加密存储
  3. 数据清理策略

    • 设置合理的过期时间
    • 提供手动清理方法
    • 应用启动时清理过期数据
  4. 错误处理

    • 捕获可能的存储错误(如超出配额)
    • 提供降级方案
  5. 性能考虑

    • 避免存储大型数据(超过1MB)
    • 复杂数据考虑分块存储
  6. 类型安全

    • 使用TypeScript确保类型安全
    • 为存储的数据定义清晰接口
相关推荐
IT_陈寒4 分钟前
Redis的Lua脚本居然把我的集群搞崩了!
前端·人工智能·后端
计算机魔术师9 分钟前
索尼与华纳起诉Anthropic,指控其大规模盗用版权音乐训练Claude
前端
恋猫de小郭14 分钟前
AI 时代,一个优化 Flutter 的重复代码工具 Deslop
android·前端·flutter
穗余14 分钟前
Typescript let和var区别
前端·javascript·typescript
晓得迷路了19 分钟前
栗子前端技术周刊第 144 期 - Rspack 2.2、pnpm 12、Solid 2.0 RC...
前端·javascript·css
YHHLAI24 分钟前
前端路由与浏览器历史:从多页应用到单页应用的演进
前端
奈斯先生Vector31 分钟前
从“能调用”到“可运营”:AI Agent 进入多模型时代后的架构升级
java·javascript·数据库·人工智能·算法·架构·aigc
Escalating_xu32 分钟前
【C++类和对象(上)】从类的定义、封装与对象模型到内存对齐和 this 指针
开发语言·前端·c++
Json____34 分钟前
宿舍安全卫生检查系统:Node 全栈开发实战
java·前端·数据库·毕业设计·课程设计·毕设·wwwoop.com