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确保类型安全
    • 为存储的数据定义清晰接口
相关推荐
S***t7141 小时前
Vue面试经验
javascript·vue.js·面试
粉末的沉淀2 小时前
css:制作带边框的气泡框
前端·javascript·css
p***h6433 小时前
JavaScript在Node.js中的异步编程
开发语言·javascript·node.js
N***73853 小时前
Vue网络编程详解
前端·javascript·vue.js
e***71673 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
程序猿小蒜3 小时前
基于springboot的的学生干部管理系统开发与设计
java·前端·spring boot·后端·spring
银空飞羽3 小时前
让Trae CN SOLO自主发挥,看看能做出一个什么样的项目
前端·人工智能·trae
Eshine、4 小时前
解决前端项目中,浏览器无法正常加载带.gz名称的文件
前端·vue3·.gz·.gz名称的js文件无法被加载
q***38514 小时前
TypeScript 与后端开发Node.js
javascript·typescript·node.js
用户47949283569154 小时前
别再当 AI 的"人肉定位器"了:一个工具让 React 组件秒定位
前端·aigc·ai编程