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确保类型安全
    • 为存储的数据定义清晰接口
相关推荐
AAA阿giao4 分钟前
从“操纵绳子“到“指挥木偶“:Vue3 Composition API 如何彻底改变前端开发范式
开发语言·前端·javascript·vue.js·前端框架·vue3·compositionapi
TextIn智能文档云平台10 分钟前
图片转文字后怎么输入大模型处理
前端·人工智能·python
专注前端30年13 分钟前
在日常开发项目中Vue与React应该如何选择?
前端·vue.js·react.js
文刀竹肃26 分钟前
DVWA -XSS(DOM)-通关教程-完结
前端·安全·网络安全·xss
lifejump30 分钟前
Pikachu | XSS
前端·xss
进击的野人34 分钟前
Vue 组件与原型链:VueComponent 与 Vue 的关系解析
前端·vue.js·面试
馬致远42 分钟前
Vue todoList案例 优化之本地存储
前端·javascript·vue.js
请叫我聪明鸭42 分钟前
CSS实现单行、多行文本超长显示 / 不超长隐藏、悬浮窗超长展示/不超长隐藏、悬浮窗手动控制样式
前端·javascript·css
blackorbird43 分钟前
苹果修复了两个在定向攻击中被利用的Webkit漏洞,其中一个与谷歌ANGLE漏洞同源
前端·webkit
席之郎小果冻43 分钟前
【04】【创建型】【聊一聊,建造者模式】
java·前端·建造者模式