如何让localStorage支持过期时间设置
前言
最近在项目开发中,遇见了大家都常会遇见的问题,让本地存储支持过期时间的设置。那相信学习前端的童鞋们都知道,我们经常是会用的 cookie 、localStorage 、sessionStorage ,它们三个都是可以用来存放数据的。这里我们就不细讲它们之间的区别,主要讲当我们设置localStorage 的时候,怎么来给它设置一个过期时间了?下来我们就开始今天的主题。封装
根据一般的业务需求,我们需要支持`新增,修改,删除`,按照初步设想,开始编写我们代码。可跳过中间部分,直接查看最后的完整版代码示例。(TS目前还有不太熟练的地方,有什么地方有问题欢迎指出)// 定义类 class NStorage { storage: Storage; constructor(name?: string) { this.storage = window[name as keyof typeof window] || window.localStorage } /** * @description 存储方法 * @param {String} key 键名 * @param {any} value 值 * @param {number} [expires] 可选,默认0,永久 */ set(key: string, value: any, expires: number = 0) { const storeValue = { value, startTime: new Date().getTime(), __expires__: expires } this.storage.setItem(key, JSON.stringify(storeValue)) } /** * @description 获取方法 * @param {String} key 键名 * @returns {any} 值 */ get(key: string) { try { const storeItem = this.storage.getItem(key) || "{}" const storeValue = JSON.parse(storeItem) const time = new Date().getTime() // 如果是永久,则直接返回 if (storeValue?.__expires__ === 0) return storeValue // 判断当前时间是否过期 if (storeValue?.startTime && (time - storeValue?.startTime >= storeValue?.__expires__)) { this.remove(key); return null; } return storeValue.value } catch (error) { return null } } /** * @description 删除方法 * @param {String} key 删除键名 */ remove(key: string) { key && this.storage.removeItem(key); } /** * @description 清除所有本地存储 */ clear() { this.storage.clear(); } }
下面是本地测试:const store = new NStorage() store.set("name", "小红", { expires: 10 }) let count = 0 const time = setInterval(() => { const name = store.get("name") console.log(name) count += 1 if (count > 10) { clearInterval(time) } }, 1000)
在控制台可以看见打印的 小红 ,3秒后localStorage 里面的值就会被删除。功能我们是实现啦,是不是就可以皆大欢喜了?有没有bug?有没有可以优化的问题了?那当然是有的了:
1. 设置的过期时间,表示的是秒?分?小时?
2. 如果多次设置同一个值,那么依据现在逻辑,每次都会重新计算时间,但是我可能只是更新值,时间不需要重新计算了?优化
定义出选项类型// 定义支持时间类型,依次:年、月、日、时、分、秒 type TUnitType = "Y" | "M" | "D" | "h" | "m" | "s" // 定义可选参类型 interface IExtraOptions { expires?: number; unit?: TUnitType, reset?: boolean } // 设置默认初始值 const EXTRA_OPTIONS: IExtraOptions = { expires: 0, // 默认永久 unit: "s", // 默认秒 reset: false // 是否重置时间 } // 根据单位和传入时间,计算出毫秒级时间 function calcTime(time: number, unit: TUnitType = "s") { let newTime = 0 switch (unit) { case "Y": newTime = time * 365 * 24 * 60 * 60 * 1000 break; case "M": // 注意⚠️:月份这里偷懒,直接使用每月30天计算 newTime = time * 30 * 24 * 60 * 60 * 1000 break; case "D": newTime = time * 24 * 60 * 60 * 1000 break; case "h": newTime = time * 60 * 60 * 1000 break; case "m": newTime = time * 60 * 1000 break; case "s": newTime = time * 1000 break; default: newTime = time break; } return newTime } **修改 set、get 方法** /** * @description 存储方法 * @param {String} key 键名 * @param {any} value 值 * @param {number} [options] 可选 * @param {Number} [options.expires] 默认0,永久 * @param {String} [options.unit] 默认"s",秒 * @param {Boolean} [options.reset] 默认false,不重置 */ set(key: string, value: any, options?: IExtraOptions) { const isExist = this.hasKey(key) const extra = Object.assign(EXTRA_OPTIONS, options) if (isExist) { // 如果存在,判断是否是需要重新设置值 const sValue = this.get(key, true) sValue.value = value sValue.__expires__ = calcTime(extra.expires || sValue.__expires__, extra.unit || sValue.__unit__) if (extra.reset) { // 存在且重新设置时间 sValue.startTime = new Date().getTime() this._set(key, sValue) } else { // 如果已经存在,且不重新计算时间,则直接修改值后存储 this._set(key, sValue) } } else { const storeValue = { value, startTime: new Date().getTime(), __expires__: calcTime(extra.expires as number, extra.unit), __unit__: extra.unit }; this._set(key, storeValue) } } /** * @description set内部方法 * @param {String} key 键名 * @param {any} value 值 */ private _set(key: string, value: any) { this.storage.setItem(key, JSON.stringify(value)); } /** * @description 获取方法 * @param {String} key 键名 * @param {Boolean} [isMerge] 可选,是否获取处理后的对象 * @returns {any} 值 */ get(key: string, isMerge: boolean = false) { try { // 不存在直接返回 null if (!key) return null const storeItem = this.storage.getItem(key) || "{}" const storeValue = JSON.parse(storeItem) const time = new Date().getTime() // 如果是永久,则直接返回 if (storeValue?.__expires__ === 0) return isMerge ? storeValue : storeValue.value; // 判断当前时间是否过期,过期则删除当前存储值 if (storeValue?.startTime && (time - storeValue?.startTime >= storeValue?.__expires__)) { this.remove(key); return null; } return isMerge ? storeValue : storeValue.value; } catch (error) { return null } } /** * @description 是否存在 * @param {String} key 键 * @returns {Boolean} true|false */ hasKey(key: string) { if (!key) return false const value = this.get(key) return value ? true : false }优化后使用介绍const store = new NStorage() // 普通设置,不过期 store.set("name", "小红") // 设置过期时间5秒 store.set("name", "小红", { expires: 5 }) // 设置过期时间5分钟,其他更换单位即可 store.set("name", "小红", { expires: 5, unit: "m" }) // 重新计算有效时长(就相当于在设置的时刻起,再往后延长之前的设置时间) store.set("name", "小红", { reset: true }) // 获取值 store.get("name") // 删除值 store.remove("name") // 删除所有值 store.clear() // 当前存储值中是否存在 store.hasKey("name")完整版代码// 定义支持时间类型,依次:年、月、日、时、分、秒 type TUnitType = "Y" | "M" | "D" | "h" | "m" | "s" // 定义可选参类型 interface IExtraOptions { expires?: number; unit?: TUnitType, reset?: boolean } // 设置默认初始值 const EXTRA_OPTIONS: IExtraOptions = { expires: 0, // 默认永久 unit: "s", // 默认秒 reset: false // 是否重置时间 } // 根据单位和传入时间,计算出毫秒级时间 function calcTime(time: number, unit: TUnitType = "s") { let newTime = 0 switch (unit) { case "Y": newTime = time * 365 * 24 * 60 * 60 * 1000 break; case "M": // 月份这里偷懒,直接使用每月30天计算 newTime = time * 30 * 24 * 60 * 60 * 1000 break; case "D": newTime = time * 24 * 60 * 60 * 1000 break; case "h": newTime = time * 60 * 60 * 1000 break; case "m": newTime = time * 60 * 1000 break; case "s": newTime = time * 1000 break; default: newTime = time break; } return newTime } class NStorage { storage: Storage; constructor(name?: string) { this.storage = window[name as keyof typeof window] || window.localStorage } /** * @description 存储方法 * @param {String} key 键名 * @param {any} value 值 * @param {number} [options] 可选 * @param {Number} [options.expires] 默认0,永久 * @param {String} [options.unit] 默认"s",秒 * @param {Boolean} [options.reset] 默认false,不重置 */ set(key: string, value: any, options?: IExtraOptions) { const isExist = this.hasKey(key) const extra = Object.assign(EXTRA_OPTIONS, options) if (isExist) { // 如果存在,判断是否是需要重新设置值 const sValue = this.get(key, true) sValue.value = value sValue.__expires__ = calcTime(extra.expires || sValue.__expires__, extra.unit || sValue.__unit__) if (extra.reset) { // 存在且重新设置时间 sValue.startTime = new Date().getTime() this._set(key, sValue) } else { // 如果已经存在,且不重新计算时间,则直接修改值后存储 this._set(key, sValue) } } else { const storeValue = { value, startTime: new Date().getTime(), __expires__: calcTime(extra.expires as number, extra.unit), __unit__: extra.unit }; this._set(key, storeValue) } } /** * @description set内部方法 * @param {String} key 键名 * @param {any} value 值 */ private _set(key: string, value: any) { this.storage.setItem(key, JSON.stringify(value)); } /** * @description 获取方法 * @param {String} key 键名 * @param {Boolean} [isMerge] 可选,是否获取处理后的对象 * @returns {any} 值 */ get(key: string, isMerge: boolean = false) { try { // 不存在直接返回 null if (!key) return null const storeItem = this.storage.getItem(key) || "{}" const storeValue = JSON.parse(storeItem) const time = new Date().getTime() // 如果是永久,则直接返回 if (storeValue?.__expires__ === 0) return isMerge ? storeValue : storeValue.value; // 判断当前时间是否过期,过期则删除当前存储值 if (storeValue?.startTime && (time - storeValue?.startTime >= storeValue?.__expires__)) { this.remove(key); return null; } return isMerge ? storeValue : storeValue.value; } catch (error) { return null } } /** * @description 删除方法 * @param {String} key 删除键名 */ remove(key: string) { key && this.storage.removeItem(key); } /** * @description 清除所有本地存储 */ clear() { this.storage.clear(); } /** * @description 是否存在 * @param {String} key 键 * @returns {Boolean} true|false */ hasKey(key: string) { if (!key) return false const value = this.get(key) return value ? true : false } }最后
里面的逻辑也可根据需求自己添加修改,代码测试我也可能存在未测到的地方,如果有问题欢迎各位大大指出!
清朝人的真实发型是怎样的?比一黑一白的阴阳头,还一言难尽古代汉族男子二十及冠,父母将男子的头发用发冠高高束起,代表成人仪式。之后虽然这个仪式不再这么隆重,但是在人们的内心也一直尊重着。毕竟是老祖宗流传下来的,人们都一直信仰着华夏的传统文
细说雍正皇帝在康熙十七年十月三十日,在一个大雪纷飞的一天,雍正皇帝出生了,他的生母是宫女小凤,按照序齿的几位皇子,雍正应该排名第四,宫廷称他为四阿哥,钮枯禄皇后非常喜欢这个孩子,她很想收养这个
42岁薛涛爱上31岁元稹,缠绵1年后被抛弃,薛涛不困于爱情天才作家张爱玲说这世上没有一样感情不是千疮百孔的。想必只有受过极深感情伤害的人,才会有如此深切的体会。然而,薛涛例外。公元809年,42岁的蜀中才女薛涛,结识了她生命里一个非常重要
安奈儿收到深交所关注函要求说明其产品相关信息是否真实12月1日晚间,安奈儿披露深交所下发的收关注函。深交所要求安奈儿说明,安奈儿水木拟推广应用的电子束接枝改性面料实际功效相关检测结论等信息是否真实准确,电子束接枝技术与其他也具有抗病
后宫佳丽三千,来例假后是怎样应付皇帝的?她们有四种方法这个世界上谁的老婆最多?当然是我们中国历代的皇帝了,皇帝后宫三宫六院,更是有三千佳丽,可以说是天天做新郎也可能到白日飞升都玩不完,毕竟每年也会有新的佳丽进宫。但是她们大多人可能一辈
雍正皇帝传(一)爱新觉罗。胤禛是康熙皇帝的第四个儿子,出生于康熙十七年,他的生母只是一个宫女,身份卑微,因为封建社会宫女的地位卑微,每天资格扶养皇子,胤禛还没出生就已经打算指派给别的妃子,他的监护
降臣杀主斩草除根的吴三桂300多年前,有人用文字记载的吴三桂是一个充满激情才华能量的男人。但之后,用来形容吴三桂的文字就远远不止这些了,取而代之的是另外儿几个更能让世人铭记于心的标签降臣汉奸忘恩负义卖主求
戈培尔洗脑鼻祖宣传天才纳粹恶棍的著名语录转发自小c漫谈戈培尔(德语PaulJosephGoebbels,18971945),德国人,文学博士,第二次世界大战期间担任德国纳粹党中央宣传部部长。戈培尔,被认为是创造希特勒的人
军营版我和我的父辈两位新兵,四代皆从军历尽战火硝烟的年代,再到新时代的中国,在信息通信部队海南某部新兵营里,有这样两位新兵,四代皆从军。这红色的血脉,这不变的忠诚,如今由胡韫涵和刘星际接续传承。今天,小编带你们听听他们
你知道新乡石榴园大街的由来吗?新乡哪条路石榴树最多?在新乡一提石榴园大街,大家都知道它是位于劳动路和胜利路之间沿着卫河走向的一条街,明明是一条小街巷,但是为什么叫石榴园大街呢?我们先说一下石榴园的来历,在唐朝的时候,居住在新乡县的大
中国历史上第一个使用间谍的国君有多牛?中国及世界史上首个第一个使用间谍的国君有多牛,一边是巴掌大的流亡之地和500名奴隶,一边是国土辽阔,兵强马壮。真正意义上的开局一条狗,装备全靠打。他是立志图强,忍辱负重的报复者。他