面試官: 如何讓localStorage支持過期時間設置?
聊到 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)
當然這種方案能解決一時的問題, 但是如果要設置任意鍵的有效期, 使用這種方案就需要編寫多個定時器, 維護成本極高, 且不利于工程化復用。
2. 中級解法
前端工程師在有一定的工作經驗之后, 往往會去考慮工程化和復用性的問題, 并對數據結構有了一定的了解, 所以可能會有接下來的解法:
- 用localStorage存一份{key(鍵): expire(過期時間)}的映射表
- 重寫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
- }
- }
- // ...
- }
眨眼一看這個方案確實解決了復用性的問題, 并且不同團隊都可以使用這個方案, 但仍然有一些缺點:
- 對 store 操作時需要維護2份數據, 并且占用緩存空間
- 如果 EXPIRE_MAP 誤刪除將會導致所有過期時間失效
- 對操作過程缺少更靈活的控制(比如操作狀態, 操作回調等)
3. 高級解法
為了減少維護成本和空間占用, 并支持一定的靈活控制和容錯能力, 我們又應該怎么做呢?
這里筆者想到了兩種類似的方案:
- 將過期時間存到 key 中, 如 dooring|6000, 每次取值時通過分隔符“|”來將 key 和 expire 取出, 進行判斷
- 將過期時間存到 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;
- },
- set: function (
- 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;
這樣, 我們就實現了每個 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');
- });
同時 xijs 還在持續擴充更有用的工具函數, 讓業務開發更高效. 目前已集成了如下工具函數:
- store 基于 localStorage 上層封裝的支持過期時間設置的緩存庫, 支持操作回調
- uuid 生成唯一id, 支持設置長度
- randomStr 生成指定個數的隨機字符串
- formatDate 開箱即用的時間格式化工具
- debounce 防抖函數
- throttle 節流函數
- url2obj 將url字符串轉換為對象
- obj2url 將對象轉換成編碼后的url字符串
- isPC 判斷設備是否為PC類型
本文轉載自微信公眾號「趣談前端」
【編輯推薦】