面试官: 如何让localStorage支持过期时间设置?

开发 前端
localStorage 属性允许我们访问一个 Document 源(origin)的对象 Storage;存储的数据将保存在浏览器会话中。

[[440024]]

聊到 localStorage 想必熟悉前端的朋友都不会陌生, 我们可以使用它提供的 getItem, setItem, removeItem, clear 这几个 API 轻松的对存储在浏览器本地的数据进行读,写, 删操作, 但是相比于 cookie, localStorage 唯一美中不足的就是不能设置每一个键的过期时间。

localStorage 属性允许我们访问一个 Document 源(origin)的对象 Storage;存储的数据将保存在浏览器会话中。localStorage 类似 sessionStorage,但其区别在于:存储在 localStorage 的数据可以长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在 sessionStorage 的数据会被清除 。

我们还应注意,localStorage 中的键值对总是以字符串的形式存储。

问题描述

在实际的应用场景中, 我们往往需要让 localStorage 设置的某个 key 能在指定时间内自动失效, 所以基于这种场景, 我们如何去解决呢?

1. 初级解法

对于刚熟悉前端的朋友, 可能会立马给出答案:

localStorage.setItem('dooring''1.0.0'
// 设置一小时的有效期 
const expire = 1000 * 60 * 60; 
setTimeout(() => { 
  localStorage.setItem('dooring'''
}, expire) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

当然这种方案能解决一时的问题, 但是如果要设置任意键的有效期, 使用这种方案就需要编写多个定时器, 维护成本极高, 且不利于工程化复用。

2. 中级解法

前端工程师在有一定的工作经验之后, 往往会去考虑工程化和复用性的问题, 并对数据结构有了一定的了解, 所以可能会有接下来的解法:

  1. 用localStorage存一份{key(键): expire(过期时间)}的映射表
  2. 重写localStorage API, 对方法进行二次封装

类似的代码如下:

const store = { 
  // 存储过期时间映射 
  setExpireMap: (key, expire) => { 
    const expireMap = localStorage.getItem('EXPIRE_MAP') || "{}" 
    localStorage.setItem( 
      'EXPIRE_MAP',  
      JSON.stringify({ 
      ...JSON.parse(expireMap), 
      key: expire 
    })) 
  }, 
  setItem: (key, value, expire) => { 
    store.setExpireMap(key, expire) 
    localStorage.setItem(key, value) 
  }, 
  getItem: (key) => { 
    // 在取值之前先判断是否过期 
    const expireMap = JSON.parse( 
      localStorage.getItem('EXPIRE_MAP') || "{}" 
    ) 
    if(expireMap[key] && expireMap[key] < Date.now()) { 
      return localStorage.getItem(key
    }else { 
      localStorage.removeItem(key
      return null 
    } 
  } 
  // ... 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

眨眼一看这个方案确实解决了复用性的问题, 并且不同团队都可以使用这个方案, 但仍然有一些缺点:

  • 对 store 操作时需要维护2份数据, 并且占用缓存空间
  • 如果 EXPIRE_MAP 误删除将会导致所有过期时间失效
  • 对操作过程缺少更灵活的控制(比如操作状态, 操作回调等)

3. 高级解法

为了减少维护成本和空间占用, 并支持一定的灵活控制和容错能力, 我们又应该怎么做呢?

这里笔者想到了两种类似的方案:

  1. 将过期时间存到 key 中, 如 dooring|6000, 每次取值时通过分隔符“|”来将 key 和 expire 取出, 进行判断
  2. 将过期时间存到 value 中, 如 1.0.0|6000, 剩下的同1

为了更具有封装性和可靠性, 我们还可以配置不同状态下的回调, 简单实现如下:

const store = { 
  preId: 'xi-'
  timeSign: '|-door-|'
  status: { 
    SUCCESS: 0, 
    FAILURE: 1, 
    OVERFLOW: 2, 
    TIMEOUT: 3, 
  }, 
  storage: localStorage || window.localStorage, 
  getKey: function (key: string) { 
    return this.preId + key
  }, 
  setfunction ( 
    key: string, 
    value: string | number, 
    time?: Date & number, 
    cb?: (status: number, key: string, value: string | number) => void, 
  ) { 
    let _status = this.status.SUCCESS, 
      _key = this.getKey(key), 
      _time; 
    // 设置失效时间,未设置时间默认为一个月 
    try { 
      _time = time 
        ? new Date(time).getTime() || time.getTime() 
        : new Date().getTime() + 1000 * 60 * 60 * 24 * 31; 
    } catch (e) { 
      _time = new Date().getTime() + 1000 * 60 * 60 * 24 * 31; 
    } 
    try { 
      this.storage.setItem(_key, _time + this.timeSign + value); 
    } catch (e) { 
      _status = this.status.OVERFLOW; 
    } 
    cb && cb.call(this, _status, _key, value); 
  }, 
  get: function ( 
    key: string, 
    cb?: (status: number, value: string | number | null) => void, 
  ) { 
    let status = this.status.SUCCESS, 
      _key = this.getKey(key), 
      value = null
      timeSignLen = this.timeSign.length, 
      that = this, 
      index
      time
      result; 
    try { 
      value = that.storage.getItem(_key); 
    } catch (e) { 
      result = { 
        status: that.status.FAILURE, 
        value: null
      }; 
      cb && cb.call(this, result.status, result.value); 
      return result; 
    } 
    if (value) { 
      index = value.indexOf(that.timeSign); 
      time = +value.slice(0, index); 
      if (time > new Date().getTime() || time == 0) { 
        value = value.slice(index + timeSignLen); 
      } else { 
        (value = null), (status = that.status.TIMEOUT); 
        that.remove(_key); 
      } 
    } else { 
      status = that.status.FAILURE; 
    } 
    result = { 
      status: status, 
      value: value, 
    }; 
    cb && cb.call(this, result.status, result.value); 
    return result; 
  }, 
  // ... 
}; 
 
export default store; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.

这样, 我们就实现了每个 key 都有独立的过期时间, 并且对不同的操作结果可以轻松的进行状态管控啦~

4. 骨灰级解法

当然, 骨灰级解法是直接使用 xijs 这个 javascript 工具库, 因为我已经将上述完整实现方案封装到该库中了, 我们只需要使用如下的方案, 就能轻松使用具有过期时间的强大的 localStorage 方法啦 :

//  先安装 yarn add xijs 
import { store } from 'xijs'
// 设置带有过期时间的key 
store.set('name''dooring'Date.now() + 1000); 
console.log(store.get('name')); 
setTimeout(() => { 
  console.log(store.get('name')); 
}, 1000); 
 
// 设置成功后的回调 
store.set('dooring''xuxiaoxi'Date.now() + 1000, (status, key, value) => { 
  console.log('success'); 
}); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

同时 xijs 还在持续扩充更有用的工具函数, 让业务开发更高效. 目前已集成了如下工具函数:

  • store 基于 localStorage 上层封装的支持过期时间设置的缓存库, 支持操作回调
  • uuid 生成唯一id, 支持设置长度
  • randomStr 生成指定个数的随机字符串
  • formatDate 开箱即用的时间格式化工具
  • debounce 防抖函数
  • throttle 节流函数
  • url2obj 将url字符串转换为对象
  • obj2url 将对象转换成编码后的url字符串
  • isPC 判断设备是否为PC类型

本文转载自微信公众号「趣谈前端」

【编辑推荐】

 

责任编辑:姜华 来源: 趣谈前端
相关推荐

2024-05-09 10:33:14

JS计算容量

2024-04-09 08:39:16

本地缓存开发线程安全

2015-08-13 10:29:12

面试面试官

2024-12-26 10:19:16

2023-02-16 08:10:40

死锁线程

2024-04-08 10:35:59

JS代码容量

2021-07-06 07:08:18

管控数据数仓

2024-04-03 00:00:00

Redis集群代码

2024-09-11 22:51:19

线程通讯Object

2010-08-12 16:28:35

面试官

2025-03-17 00:00:00

2023-11-20 10:09:59

2021-03-01 18:42:02

缓存LRU算法

2024-02-20 14:10:55

系统缓存冗余

2024-03-18 14:06:00

停机Spring服务器

2019-07-23 09:30:17

HTTP 2.0HTTP协议传输

2024-04-23 14:09:59

JavaScript开发

2021-02-06 09:21:17

MySQL索引面试

2019-04-29 14:59:41

Tomcat系统架构

2022-05-23 08:43:02

BigIntJavaScript内置对象
点赞
收藏

51CTO技术栈公众号