前端本地存储方案详解

56 阅读6分钟

一、本地存储方案对比概览

方案对比 二、各方案详细介绍及示例

1. Cookie

特点:最早的前端存储方案,每次请求自动携带到服务器

// 设置Cookie
document.cookie = "username=John; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/";

// 读取Cookie
function getCookie(name) {
  const cookies = document.cookie.split(';');
  for (let cookie of cookies) {
    const [key, value] = cookie.trim().split('=');
    if (key === name) return decodeURIComponent(value);
  }
  return null;
}

// 封装Cookie操作类
class CookieManager {
  static set(name, value, days = 7) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    const expires = `expires=${date.toUTCString()}`;
    document.cookie = `${name}=${encodeURIComponent(value)};${expires};path=/`;
  }

  static get(name) {
    return getCookie(name);
  }

  static delete(name) {
    document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
  }
}

2. Web Storage (LocalStorage & SessionStorage)

特点:键值对存储,简单易用

// LocalStorage 示例(持久化存储)
class LocalStorageManager {
  static set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (e) {
      console.error('LocalStorage存储失败:', e);
      return false;
    }
  }

  static get(key) {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : null;
    } catch (e) {
      console.error('LocalStorage读取失败:', e);
      return null;
    }
  }

  static remove(key) {
    localStorage.removeItem(key);
  }

  static clear() {
    localStorage.clear();
  }

  // 获取剩余存储空间(近似值)
  static getRemainingSpace() {
    const testKey = 'test';
    const data = '1'.repeat(1024 * 1024); // 1MB数据
    try {
      localStorage.setItem(testKey, data);
      localStorage.removeItem(testKey);
      return '充足';
    } catch (e) {
      return '不足';
    }
  }
}

// SessionStorage 示例(会话级存储)
const SessionStorageManager = {
  // 存储登录状态
  setLoginState(userData) {
    sessionStorage.setItem('auth', JSON.stringify({
      user: userData,
      timestamp: Date.now(),
      expiresIn: 3600000 // 1小时
    }));
  },

  // 检查登录状态
  checkLogin() {
    const auth = sessionStorage.getItem('auth');
    if (!auth) return null;
    
    const { user, timestamp, expiresIn } = JSON.parse(auth);
    if (Date.now() - timestamp > expiresIn) {
      sessionStorage.removeItem('auth');
      return null;
    }
    return user;
  }
};

3. IndexedDB

特点:非关系型数据库,支持事务、索引,适合大量结构化数据

class IndexedDBManager {
  constructor(dbName = 'AppDatabase', version = 1) {
    this.dbName = dbName;
    this.version = version;
    this.db = null;
  }

  // 初始化数据库
  async init(stores = [{ name: 'users', keyPath: 'id' }]) {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve(this.db);
      };

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        stores.forEach(store => {
          if (!db.objectStoreNames.contains(store.name)) {
            db.createObjectStore(store.name, { keyPath: store.keyPath });
          }
        });
      };
    });
  }

  // 添加/更新数据
  async put(storeName, data) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.put(data);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  // 获取数据
  async get(storeName, key) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      const request = store.get(key);

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  // 获取所有数据
  async getAll(storeName) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      const request = store.getAll();

      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  // 删除数据
  async delete(storeName, key) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.delete(key);

      request.onsuccess = () => resolve(true);
      request.onerror = () => reject(request.error);
    });
  }
}

// 使用示例
async function example() {
  const db = new IndexedDBManager('MyApp', 2);
  await db.init([
    { name: 'users', keyPath: 'id' },
    { name: 'products', keyPath: 'sku' }
  ]);

  // 添加用户
  await db.put('users', {
    id: 1,
    name: '张三',
    email: 'zhangsan@example.com',
    preferences: { theme: 'dark', language: 'zh-CN' }
  });

  // 查询用户
  const user = await db.get('users', 1);
  console.log(user);
}

4. Cache API

特点:用于缓存网络请求,常用于PWA和Service Worker

// Service Worker中的缓存管理
class CacheManager {
  static CACHE_NAME = 'app-cache-v1';

  // 缓存静态资源
  static async cacheStaticAssets() {
    const cache = await caches.open(this.CACHE_NAME);
    return cache.addAll([
      '/',
      '/index.html',
      '/styles/main.css',
      '/scripts/app.js',
      '/images/logo.png'
    ]);
  }

  // 缓存API响应
  static async cacheAPIRequest(request, response) {
    const cache = await caches.open(this.CACHE_NAME);
    await cache.put(request, response.clone());
  }

  // 获取缓存响应(网络优先)
  static async getCacheFirst(request) {
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
      return cachedResponse;
    }

    try {
      const networkResponse = await fetch(request);
      // 可选:缓存这个响应
      if (networkResponse.ok) {
        const cache = await caches.open(this.CACHE_NAME);
        cache.put(request, networkResponse.clone());
      }
      return networkResponse;
    } catch (error) {
      // 网络失败,返回降级内容
      return new Response(JSON.stringify({ 
        error: '网络不可用,使用缓存失败' 
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
  }

  // 清理旧缓存
  static async cleanOldCaches() {
    const cacheNames = await caches.keys();
    await Promise.all(
      cacheNames.map(cacheName => {
        if (cacheName !== this.CACHE_NAME) {
          return caches.delete(cacheName);
        }
      })
    );
  }
}

三、优缺点详细对比

1. Cookie

优点:

兼容性极好(所有浏览器)

自动随请求发送到服务器

可设置过期时间、域名、路径等属性

支持HTTP-only(防XSS)和Secure(仅HTTPS)标记

缺点:

容量小(4KB)

每次请求都携带,增加流量消耗

安全性问题(可能被CSRF攻击)

API简陋,操作不便

同步操作可能阻塞页面

2. LocalStorage

优点:

容量较大(5-10MB)

操作简单,API友好

同源页面共享

持久化存储,手动清理

缺点:

仅存储字符串,需要JSON转换

同步操作,大量数据可能阻塞页面

无法在Web Worker中访问

无数据过期机制

安全性较低(易受XSS攻击)

3. SessionStorage

优点:

会话隔离,隐私性好

页面刷新不影响数据

操作简单,API友好

缺点:

容量限制(5-10MB)

标签页关闭数据丢失

同步操作

无法跨标签页共享

4. IndexedDB

优点:

容量大(通常250MB以上)

支持事务,保证数据一致性

异步操作,不阻塞页面

支持复杂查询和索引

可在Web Worker中使用

缺点:

API复杂,学习曲线陡峭

兼容性相对较差(但现代浏览器都支持)

需要手动管理数据库版本

调试相对困难

5. Cache API

优点:

专门为网络请求缓存设计

支持请求/响应对象存储

Service Worker集成

离线访问支持

缺点:

主要用于缓存网络资源

不适合存储应用状态数据

Service Worker要求HTTPS

管理相对复杂

四、综合总结与建议

选择策略 策略对比 最佳实践示例

1. 安全存储策略

// 封装安全的存储管理器
class SecureStorage {
  constructor(storage = localStorage) {
    this.storage = storage;
  }

  // 加密存储(简单示例,生产环境使用更强大的加密)
  set(key, value, encrypt = false) {
    try {
      const data = encrypt ? this.encrypt(value) : value;
      this.storage.setItem(key, JSON.stringify({
        data,
        encrypted: encrypt,
        timestamp: Date.now()
      }));
    } catch (e) {
      console.error('存储失败:', e);
    }
  }

  get(key) {
    try {
      const item = JSON.parse(this.storage.getItem(key));
      if (!item) return null;
      
      return item.encrypted ? this.decrypt(item.data) : item.data;
    } catch (e) {
      console.error('读取失败:', e);
      return null;
    }
  }

  encrypt(text) {
    // 实际项目应使用更安全的加密算法
    return btoa(encodeURIComponent(text));
  }

  decrypt(text) {
    return decodeURIComponent(atob(text));
  }
}

2. 存储容量管理

// 存储容量监控和管理
class StorageMonitor {
  static checkLocalStorageQuota() {
    try {
      // 测试存储容量
      const testData = '1'.repeat(1024 * 1024); // 1MB
      localStorage.setItem('__test__', testData);
      localStorage.removeItem('__test__');
      return '空间充足';
    } catch (e) {
      // 清理旧数据
      this.cleanupOldData();
      return '空间不足,已清理旧数据';
    }
  }

  static cleanupOldData() {
    const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
    const keys = Object.keys(localStorage);
    
    keys.forEach(key => {
      try {
        const item = JSON.parse(localStorage.getItem(key));
        if (item && item.timestamp && item.timestamp < oneWeekAgo) {
          localStorage.removeItem(key);
        }
      } catch (e) {
        // 非JSON数据,根据需求处理
      }
    });
  }
}

3. 混合存储方案示例

// 根据数据类型智能选择存储方案
class HybridStorage {
  constructor() {
    this.schemes = {
      cookie: { maxSize: 4 * 1024 },
      localStorage: { maxSize: 5 * 1024 * 1024 },
      indexedDB: { maxSize: Infinity }
    };
  }

  async store(key, value, options = {}) {
    const size = this.getSize(value);
    
    if (size <= this.schemes.cookie.maxSize && options.sendToServer) {
      // 小数据且需要服务器访问 -> Cookie
      CookieManager.set(key, value, options.expires);
    } else if (size <= this.schemes.localStorage.maxSize) {
      // 中等数据 -> LocalStorage
      LocalStorageManager.set(key, value);
    } else {
      // 大数据 -> IndexedDB
      const db = new IndexedDBManager();
      await db.init();
      await db.put('hybridData', { key, value, timestamp: Date.now() });
    }
  }

  getSize(obj) {
    return new Blob([JSON.stringify(obj)]).size;
  }
}

建议

  1. 简单应用:优先使用LocalStorage,注意数据大小和安全性

  2. 复杂应用:使用IndexedDB存储结构化数据,LocalStorage存储配置信息

  3. 需要服务器通信:使用Cookie存储会话标识等小数据

  4. PWA/离线应用:Cache API + IndexedDB组合

  5. 敏感数据:避免在前端存储,必须存储时进行加密

  6. 定期清理:实现数据过期和清理机制

  7. 错误处理:所有存储操作都要有try-catch

  8. 降级策略:考虑存储失败时的降级方案

根据具体项目需求选择合适的存储方案,多数情况下组合使用多种方案能达到最佳效果。

在这里插入图片描述