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确保类型安全
    • 为存储的数据定义清晰接口
相关推荐
前端大卫21 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘37 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare38 分钟前
浅浅看一下设计模式
前端
Lee川41 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端