基于 localStorage 实现有过期时间的存储方式

蚊子前端博客
发布于 2023-03-01 00:27
我们介绍下如何基于 localStorage 实现有过期时间的存储方式!

我们知道 localStorage 中的数据是长期保存的,除非手动删除,否则他会一直存在。如果我们想实现一种数据有过期时间的存储方式,该怎么实现呢?

首先应该想到的是 cookie,cookie 本身就有有效期的配置,当某个 cookie 时,浏览器自动清理该 cookie。可是使用 cookie 存储数据,有个不好的地方,很多我们存储的数据,本就是我们前端自己用到的,后端根本用不到。可是存储到 cookie 中后,页面中所有的 cookie 都会随着请求发送给后端,造成传输的 cookie 比较长,而且没有必要。

低调低调-蚊子的前端博客

因此,我们可以基于 localStorage 来实现一套这样的有过期时间的存储方式。我们在之前的文章 如何重写 localStorage 中的方法 中,也了解了一些重写 localStorage 的方法。这里我们是自己在外层封装一层的方式,来调用 localStorage。

我这里封装的类名叫: LocalExpiredStorage,即有过期时间的 localStorage。

1. 实现与 localStorage 基本一致的 api

我们为了实现跟 localStorage 使用上的一致性体验,这里我们自己的 api 名称和实现方式跟 localStorage 基本一致。

COPYTYPESCRIPT

interface SetItemOptions { maxAge?: number; // 从当前时间往后多长时间过期 expired?: number; // 过期的准确时间点,优先级比maxAge高 } class LocalExpiredStorage { private prefix = "local-expired-"; // 用于跟没有过期时间的key进行区分 constructor(prefix?: string) { if (prefix) { this.prefix = prefix; } } setItem(key: string, value: any, options?: SetItemOptions) {} getItem(key: string): any {} removeItem(key: string) {} clearAllExpired() {} } const localExpiredStorage = new LocalExpiredStorage(); export default localExpiredStorage;

可以看到我们实现的类里,有三个变化:

  1. setItem()方法新增了一个 options 参数,这里主要是为了配置过期时间,这里有两种配置方式,一种是可以设置多长时间后过期,比如 2 个小时后过期(开发者不用特殊计算 2 个小时后的时间节点);再一种是设置过期的时间节点,该值可以是格式化的时间,也可以是时间戳;

  2. 有一个 prefix 属性,在具体实现中,我们会将 prefix 属性与操作的 key 进行拼接,标识该 key 是具有过期时间特性的,方便我们自己的类进行处理;

  3. 新增了一个 clearAllExpired() 方法,这是为了清理所有已经过期的 key,避免占用缓存;该方法在应用的入口处就应当调用,便于及时清理;

上面是我们的大致框架,接下来我们来具体实现下这些方法。

干饭-蚊子的前端博客

2. 具体实现

接下来我们来一一实现这些方法。

2.1 setItem

这里我们新增了一个 options 参数,用来配置过期时间:

  • expired: 固定的过期时间点,比如点击关闭按钮,当天不再展示,那过期时间就是今天晚上的 23:59:59,可以使用该属性;

  • maxAge: 从当前时间起,设置多长时间后过期;比如点击某个提示,3 天内不再展示,使用该属性就比较方便;

假如两个属性都设置了,我这里约定 expired 属性的优先级更高一些。

COPYTYPESCRIPT

class LocalExpiredStorage { private prefix = "local-expired-"; // 用于跟没有过期时间的key进行区分 constructor(prefix?: string) { if (prefix) { this.prefix = prefix; } } setItem(key: string, value: any, options?: SetItemOptions) { const now = Date.now(); let expired = now + 1000 * 60 * 60 * 3; // 默认过期时间为3个小时 // 这里我们限定了 expired 和 maxAge 都是 number 类型, // 您也可以扩展支持到 string 类型或者如 { d:2, h:3 } 这种格式 if (options?.expired) { expired = options?.expired; } else if (options?.maxAge) { expired = now + options.maxAge; } // 我们这里用了 dayjs 对时间戳进行格式化,方便快速识别 // 若没这个需要,也可以直接存储时间戳,减少第三方类库的依赖 localStorage.setItem( `${this.prefix}${key}`, JSON.stringify({ value, start: dayjs().format("YYYY/MM/DD hh:mm:ss"), // 存储的起始时间 expired: dayjs(expired).format("YYYY/MM/DD hh:mm:ss"), // 存储的过期时间 }) ); } }

我们在过期时间的实现过程中,目前只支持了 number 类型,即需要传入一个时间戳,参与运算。您也可以扩展到 string 类型(比如'2024/11/23 14:45:34')或者其他格式{ d:2, h:3 } 这种格式。

设置好过期时间后,我们将 value,存储的起始时间和过期时间,转义成 json string 存储起来。我们这里用了 dayjs 对时间戳进行格式化,方便开发者可以快速地识别。若没有这个需要,也可以直接存储时间戳,减少第三方类库的依赖。

该方法并没有支持永久存储的设定,若您需要永久存储,可以直接使用 localStorage 来存储。

2.2 getItem

获取某 key 存储的值,主要是对过期时间的判断。

COPYTYPESCRIPT

class LocalExpiredStorage { private prefix = "local-expired-"; // 用于跟没有过期时间的key进行区分 constructor(prefix?: string) { if (prefix) { this.prefix = prefix; } } getItem(key: string): any { const result = localStorage.getItem(`${this.prefix}${key}`); if (!result) { // 若key本就不存在,直接返回null return result; } const { value, expired } = JSON.parse(result); if (Date.now() <= dayjs(expired).valueOf()) { // 还没过期,返回存储的值 return value; } // 已过期,删除该key,然后返回null this.removeItem(key); return null; } removeItem(key: string) { localStorage.removeItem(`${this.prefix}${key}`); } }

在获取 key 时,主要经过 3 个过程:

  1. 若本身就没存储这个 key,直接返回 null;

  2. 已存储了该 key 的数据,解析出数据和过期时间,若还在有效期,则返回存储大数据;

  3. 若已过期,则删除该 key,然后返回 null;

这里我们在删除数据时,使用了this.removeItem(),即自己实现的删除方法。本来我们也是要实现这个方法的,那就直接使用了吧。

2.3 clearAllExpired

localStorage 中的数据并不会自动清理,我们需要一个方法用来手动批量清理已过期的数据。

COPYTYPESCRIPT

class LocalExpiredStorage { private prefix = "local-expired-"; // 用于跟没有过期时间的key进行区分 clearAllExpired() { let num = 0; // 判断 key 是否过期,然后删除 const delExpiredKey = (key: string, value: string | null) => { if (value) { // 若value有值,则判断是否过期 const { expired } = JSON.parse(value); if (Date.now() > dayjs(expired).valueOf()) { // 已过期 localStorage.removeItem(key); return 1; } } else { // 若 value 无值,则直接删除 localStorage.removeItem(key); return 1; } return 0; }; const { length } = window.localStorage; const now = Date.now(); for (let i = 0; i < length; i++) { const key = window.localStorage.key(i); if (key?.startsWith(this.prefix)) { // 只处理我们自己的类创建的key const value = window.localStorage.getItem(key); num += delExpiredKey(key, value); } } return num; } }

在项目的入口处添加上该方法,用户每次进入项目时,都会自动清理一次已过期的 key。

醒一醒-蚊子的前端博客

3. 完整的代码

上面我们是分步讲解的,这里我们放下完整的代码。同时,我也在 GitHub 上放了一份:wenzi0github/local-expired-storage

COPYTYPESCRIPT

interface SetItemOptions { maxAge?: number; // 从当前时间往后多长时间过期 expired?: number; // 过期的准确时间点,优先级比maxAge高 } class LocalExpiredStorage { private prefix = "local-expired-"; // 用于跟没有过期时间的key进行区分 constructor(prefix?: string) { if (prefix) { this.prefix = prefix; } } // 设置数据 setItem(key: string, value: any, options?: SetItemOptions) { const now = Date.now(); let expired = now + 1000 * 60 * 60 * 3; // 默认过期时间为3个小时 // 这里我们限定了 expired 和 maxAge 都是 number 类型, // 您也可以扩展支持到 string 类型或者如 { d:2, h:3 } 这种格式 if (options?.expired) { expired = options?.expired; } else if (options?.maxAge) { expired = now + options.maxAge; } // 我们这里用了 dayjs 对时间戳进行格式化,方便快速识别 // 若没这个需要,也可以直接存储时间戳,减少第三方类库的依赖 localStorage.setItem( `${this.prefix}${key}`, JSON.stringify({ value, start: dayjs().format("YYYY/MM/DD hh:mm:ss"), // 存储的起始时间 expired: dayjs(expired).format("YYYY/MM/DD hh:mm:ss"), // 存储的过期时间 }) ); } getItem(key: string): any { const result = localStorage.getItem(`${this.prefix}${key}`); if (!result) { // 若key本就不存在,直接返回null return result; } const { value, expired } = JSON.parse(result); if (Date.now() <= dayjs(expired).valueOf()) { // 还没过期,返回存储的值 return value; } // 已过期,删除该key,然后返回null this.removeItem(key); return null; } // 删除key removeItem(key: string) { localStorage.removeItem(`${this.prefix}${key}`); } // 清除所有过期的key clearAllExpired() { let num = 0; // 判断 key 是否过期,然后删除 const delExpiredKey = (key: string, value: string | null) => { if (value) { // 若value有值,则判断是否过期 const { expired } = JSON.parse(value); if (Date.now() > dayjs(expired).valueOf()) { // 已过期 localStorage.removeItem(key); return 1; } } else { // 若 value 无值,则直接删除 localStorage.removeItem(key); return 1; } return 0; }; const { length } = window.localStorage; const now = Date.now(); for (let i = 0; i < length; i++) { const key = window.localStorage.key(i); if (key?.startsWith(this.prefix)) { // 只处理我们自己的类创建的key const value = window.localStorage.getItem(key); num += delExpiredKey(key, value); } } return num; } } const localExpiredStorage = new LocalExpiredStorage(); export default localExpiredStorage;

使用:

COPYJAVASCRIPT

localExpiredStorage.setItem("key", "value", { maxAge: 5000 }); // 有效期为5000毫秒 localExpiredStorage.setItem("key", "value", { expired: Date.now() + 1000 * 60 * 60 * 12, }); // 有效期为 12 个小时,自己计算到期的时间戳 // 获取数据 localExpiredStorage.getItem("key"); // 删除数据 localExpiredStorage.removeItem("key"); // 清理所有过期的key localExpiredStorage.clearAllExpired();

4. 总结

这个功能本身不难,也有很多开发者自己实现过。这里我也是总结下之前实现的过程。

标签:
阅读(279)

公众号:

qrcode

微信公众号:前端小茶馆

公众号:

qrcode

微信公众号:前端小茶馆