JavaScript 实现支持过期时间的数据缓存功能

JavaScript 实现支持过期时间的数据缓存功能

要在 JavaScript 中实现数据缓存功能并支持设置过期时间,可以使用 localStoragesessionStorage 或内存对象(如 Map 或普通对象)来存储数据,并为每个缓存项设置一个过期时间。以下是一个简单的实现示例:


JavaScript 实现支持过期时间的数据缓存功能

1. 使用 localStorage 实现持久缓存

const Cache = {
  /**
   * 设置缓存
   * @param {string} key - 缓存键
   * @param {*} value - 缓存值
   * @param {number} ttl - 缓存时间(毫秒)
   */
  set(key, value, ttl) {
    const data = {
      value,
      expiry: ttl ? Date.now() + ttl : null, // 计算过期时间
    };
    localStorage.setItem(key, JSON.stringify(data));
  },

  /**
   * 获取缓存
   * @param {string} key - 缓存键
   * @returns {*} 缓存值或 null(如果过期或不存在)
   */
  get(key) {
    const data = localStorage.getItem(key);
    if (!data) return null;

    try {
      const { value, expiry } = JSON.parse(data);
      if (expiry && Date.now() > expiry) {
        localStorage.removeItem(key); // 过期删除缓存
        return null;
      }
      return value;
    } catch (e) {
      console.warn("缓存数据解析失败", e);
      return null;
    }
  },

  /**
   * 删除缓存
   * @param {string} key - 缓存键
   */
  remove(key) {
    localStorage.removeItem(key);
  },

  /**
   * 清空所有缓存
   */
  clear() {
    localStorage.clear();
  },
};

// 使用示例
Cache.set("username", "Alice", 5000); // 设置缓存5秒后过期
console.log(Cache.get("username")); // 5秒内返回 'Alice'
setTimeout(() => console.log(Cache.get("username")), 6000); // 6秒后返回 null

⚠️ 注意事项

  • localStorage 只能存储字符串,因此要使用 JSON.stringifyJSON.parse 进行序列化和反序列化。
  • localStorage 的存储空间一般有限(大约 5MB)。
  • 如果是跨页面使用,请确保在相同的域名下。

2. 使用 sessionStorage 实现数据缓存(适合页面级临时存储)

sessionStorage 是一种浏览器存储机制,它的特点是数据仅在页面会话(session) 期间有效,页面关闭后数据会被清除。

const SessionCache = {
  /**
   * 设置缓存
   * @param {string} key - 缓存键
   * @param {*} value - 缓存值
   * @param {number} ttl - 缓存时间(毫秒)
   */
  set(key, value, ttl) {
    const data = {
      value,
      expiry: ttl ? Date.now() + ttl : null, // 计算过期时间
    };
    sessionStorage.setItem(key, JSON.stringify(data));
  },

  /**
   * 获取缓存
   * @param {string} key - 缓存键
   * @returns {*} 缓存值或 null(如果过期或不存在)
   */
  get(key) {
    const data = sessionStorage.getItem(key);
    if (!data) return null;

    try {
      const { value, expiry } = JSON.parse(data);
      if (expiry && Date.now() > expiry) {
        sessionStorage.removeItem(key); // 缓存已过期,删除
        return null;
      }
      return value;
    } catch (e) {
      console.warn("缓存数据解析失败:", e);
      return null;
    }
  },

  /**
   * 删除缓存
   * @param {string} key - 缓存键
   */
  remove(key) {
    sessionStorage.removeItem(key);
  },

  /**
   * 清空所有缓存
   */
  clear() {
    sessionStorage.clear();
  },
};

// **使用示例**
// 设置缓存,5秒后过期
SessionCache.set("userToken", "abc123", 5000);

// 获取缓存
console.log(SessionCache.get("userToken")); // 5秒内返回 'abc123'

// 5秒后尝试获取缓存
setTimeout(() => {
  console.log(SessionCache.get("userToken")); // 返回 null
}, 6000);

⚠️ 注意事项

  • sessionStorage 数据在页面关闭或会话结束时自动清除。
  • 在不同的标签页中,sessionStorage独立的(不会共享)。
  • sessionStorage 的存储空间一般为5MB左右。
  • 数据存储在 sessionStorage 时必须经过 JSON.stringifyJSON.parse 处理。

3. 使用内存对象实现轻量缓存(适合短期缓存)

class MemoryCache {
  constructor() {
    this.cache = new Map();
  }

  /**
   * 设置缓存
   * @param {string} key - 缓存键
   * @param {*} value - 缓存值
   * @param {number} ttl - 缓存时间(毫秒)
   */
  set(key, value, ttl) {
    const expiry = ttl ? Date.now() + ttl : null;
    this.cache.set(key, { value, expiry });
  }

  /**
   * 获取缓存
   * @param {string} key - 缓存键
   * @returns {*} 缓存值或 null(如果过期或不存在)
   */
  get(key) {
    const item = this.cache.get(key);
    if (!item) return null;

    if (item.expiry && Date.now() > item.expiry) {
      this.cache.delete(key); // 缓存过期,删除
      return null;
    }
    return item.value;
  }

  /**
   * 删除缓存
   * @param {string} key - 缓存键
   */
  remove(key) {
    this.cache.delete(key);
  }

  /**
   * 清空所有缓存
   */
  clear() {
    this.cache.clear();
  }
}

// 使用示例
const memCache = new MemoryCache();
memCache.set("token", "abc123", 3000); // 设置缓存3秒后过期
console.log(memCache.get("token")); // 3秒内返回 'abc123'
setTimeout(() => console.log(memCache.get("token")), 4000); // 4秒后返回 null

4. 使用 IndexedDB 实现持久缓存(支持二进制数据)

// 初始化 IndexedDB 数据库
class IndexedDBCache {
  constructor(dbName = "CacheDB", storeName = "CacheStore") {
    this.dbName = dbName;
    this.storeName = storeName;
    this.db = null;
  }

  /**
   * 初始化数据库
   */
  async init() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: "key" });
        }
      };

      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve(this);
      };

      request.onerror = (event) => {
        console.error("IndexedDB 初始化失败:", event.target.error);
        reject(event.target.error);
      };
    });
  }

  /**
   * 获取对象存储
   */
  getObjectStore(mode = "readonly") {
    const transaction = this.db.transaction(this.storeName, mode);
    return transaction.objectStore(this.storeName);
  }

  /**
   * 添加或更新缓存
   * @param {string} key - 缓存键
   * @param {*} value - 缓存值
   * @param {number} ttl - 过期时间(毫秒)
   */
  async set(key, value, ttl) {
    const expiry = ttl ? Date.now() + ttl : null;
    const record = { key, value, expiry };

    return new Promise((resolve, reject) => {
      const store = this.getObjectStore("readwrite");
      const request = store.put(record);

      request.onsuccess = () => resolve(true);
      request.onerror = (event) => {
        console.error("数据写入失败:", event.target.error);
        reject(event.target.error);
      };
    });
  }

  /**
   * 获取缓存
   * @param {string} key - 缓存键
   * @returns {*} 缓存值或 null
   */
  async get(key) {
    return new Promise((resolve, reject) => {
      const store = this.getObjectStore("readonly");
      const request = store.get(key);

      request.onsuccess = (event) => {
        const record = event.target.result;
        if (!record) {
          resolve(null); // 数据不存在
          return;
        }

        if (record.expiry && Date.now() > record.expiry) {
          // 数据过期
          this.delete(key).then(() => resolve(null));
        } else {
          resolve(record.value); // 返回未过期数据
        }
      };

      request.onerror = (event) => {
        console.error("数据读取失败:", event.target.error);
        reject(event.target.error);
      };
    });
  }

  /**
   * 删除缓存
   * @param {string} key - 缓存键
   */
  async delete(key) {
    return new Promise((resolve, reject) => {
      const store = this.getObjectStore("readwrite");
      const request = store.delete(key);

      request.onsuccess = () => resolve(true);
      request.onerror = (event) => {
        console.error("数据删除失败:", event.target.error);
        reject(event.target.error);
      };
    });
  }

  /**
   * 清空缓存
   */
  async clear() {
    return new Promise((resolve, reject) => {
      const store = this.getObjectStore("readwrite");
      const request = store.clear();

      request.onsuccess = () => resolve(true);
      request.onerror = (event) => {
        console.error("缓存清空失败:", event.target.error);
        reject(event.target.error);
      };
    });
  }
}

// 使用示例
(async () => {
  // 创建缓存实例并初始化数据库
  const cache = new IndexedDBCache();
  await cache.init();

  // 设置缓存,数据 5 秒后过期
  await cache.set("userToken", "abc123", 5000);
  console.log("缓存设置完成");

  // 缓存二进制数据
  const binaryData = new Blob(["Hello, IndexedDB"], { type: "text/plain" });
  await cache.set("binary", binaryData, 5000);

  // 获取缓存
  console.log("立即获取:", await cache.get("userToken")); // 输出: abc123
  console.log("立即获取:", await cache.get("binary"));

  // 等待 6 秒后尝试获取缓存
  setTimeout(async () => {
    console.log("6 秒后获取:", await cache.get("userToken")); // 输出: null
    console.log("6 秒后获取:", await cache.get("binary")); // 输出: null
  }, 6000);
})();

🔑 功能说明

  1. set(key, value, ttl)

    • 添加或更新缓存。
    • ttl(毫秒):设置缓存的过期时间,null 表示永久有效。
  2. get(key)

    • 读取缓存时会检查过期时间。
    • 如果数据已过期,会自动删除并返回 null
  3. delete(key)

    • 删除指定的缓存。
  4. clear()

    • 清空所有缓存。
  5. 异步操作

    • 所有方法基于 Promise,方便与现代异步代码(async/await)结合使用。

🌟 优点

  1. 支持过期时间:在获取数据时会自动检查并清除过期数据。
  2. 高效查询:利用 IndexedDB 的键值对存储,快速读写数据。
  3. 持久化存储:数据保存在用户设备上,关闭浏览器后依然存在。

⚠️ 注意事项

  1. 浏览器兼容性:确保目标用户的浏览器支持 IndexedDB(现代浏览器普遍支持)。
  2. 性能优化:如果有大量过期数据,可以定期批量清理(如通过后台任务)。
  3. 容量限制:大多数浏览器允许 IndexedDB 使用数百 MB 的存储空间,但仍需谨慎使用以避免存储满溢。

四种方式的对比

特性/方式 localStorage sessionStorage IndexedDB 内存缓存 (Map)
持久化能力 长期持久化,直到手动清除 会话级存储,页面关闭即清除 长期持久化,直到手动清除或删除 无持久化能力,页面刷新即丢失
存储容量 约 5MB 约 5MB 通常 >50MB,具体视浏览器实现而定 受内存限制,大小不固定
过期时间实现 需手动编码支持 需手动编码支持 原生支持复杂数据操作,需手动编码 直接使用定时器或清理机制实现
数据存取性能 高效,键值直接存取 高效,键值直接存取 高效,但比 localStorage 略慢 极高,内存操作无需异步
异步操作支持 不支持,操作是同步的 不支持,操作是同步的 原生异步,基于事件或 Promise 不需要,直接操作内存
复杂数据支持 必须使用 JSON.stringify/parse 必须使用 JSON.stringify/parse 支持复杂对象、Blob、ArrayBuffer 支持复杂对象,但无法序列化存储
跨页面共享
适合场景 跨页面长期数据存储(如用户设置) 单页面会话数据存储(如表单数据) 离线存储、大量复杂数据、文件缓存 短期临时数据、高性能场景
过期机制复杂度 中等(需要检查时间并清除过期数据) 中等(需要检查时间并清除过期数据) 较高(通过索引或批量检查清理) 简单(可用定时器直接清除)

💡 选择建议

场景 建议选择 理由
短期缓存,高性能需求 内存缓存(Map) 速度最快,适合临时存储。
会话级缓存,简单键值对 sessionStorage 页面关闭后自动清除,无需手动管理。
长期缓存,简单数据 localStorage 数据持久化存储,适合小型缓存。
长期缓存,复杂或大量数据 IndexedDB 可管理大数据量,支持复杂数据结构与索引。
posted @ 2025-01-07 10:37  飞仔FeiZai  阅读(171)  评论(0)    收藏  举报