Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 104 题:模拟 localStorage 时如何实现过期时间功能 #171

Open
yygmind opened this issue Jul 12, 2019 · 23 comments
Open

第 104 题:模拟 localStorage 时如何实现过期时间功能 #171

yygmind opened this issue Jul 12, 2019 · 23 comments

Comments

@yygmind
Copy link
Contributor

yygmind commented Jul 12, 2019

No description provided.

@yygmind yygmind added the 阿里 label Jul 12, 2019
@xiemeng
Copy link

xiemeng commented Jul 12, 2019

// 模拟实现一个 localStorage
const localStorage = (function(){
let store = {};
return {
getItem(key){
if(store[key] && store[key+'time']){
const date = new Date().valueOf();
if(date>store[key+'time']){ // 过期了
this.removeItem(key);
return '已经过期了';
}
}
return store[key] || null;
},
setItem(key,value,time){
store[key] = value.toString();
if(time)store[key+'time'] = time; // 设置过期时间
},
removeItem(key){
delete store[key]
},
clear(){
store = {};
}
}
})()

@EnergySUD
Copy link

EnergySUD commented Jul 12, 2019

		const localStorageMock = (function() {
			let store = {}
			return {
				getItem: function(key) { return store[key] || null },
				setItem: function(key, value, time) { // time 是毫秒级别,过时时间必须大于0ms
					time = Number(time)?time:0;
					store[key] = value.toString()
					if(time>0){this.timeOut(key,time)}
				},
				timeOut:function(key,time){
					let that = this;
					let timer = setTimeout(function(){ that.removeItem(key);clearTimeout(timer);},time)
				},
				removeItem: function(key) { delete store[key] },
				clear: function() { store = {} },
			}
		})()

		Object.defineProperty(window, 'localStorage2', {
			value: localStorageMock
		})
		
		
		localStorage2.setItem('test',"test",3000)
		console.log(localStorage2.getItem("test"))  //test
		
		setTimeout(function(){
			console.log(localStorage2.getItem("test"))  //null
		},4000);

@3110160
Copy link

3110160 commented Jul 12, 2019

定时器没有清除呢

@zfowed
Copy link

zfowed commented Jul 12, 2019

用 cookie 模拟 localStorage

参考 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage

if (!window.localStorage) {
  window.localStorage = {
    getItem: function (sKey) {
      if (!sKey || !this.hasOwnProperty(sKey)) { return null; }
      return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1"));
    },
    key: function (nKeyId) {
      return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "").split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]);
    },
    setItem: function (sKey, sValue) {
      if(!sKey) { return; }
      document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/";
      this.length = document.cookie.match(/\=/g).length;
    },
    length: 0,
    removeItem: function (sKey) {
      if (!sKey || !this.hasOwnProperty(sKey)) { return; }
      document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
      this.length--;
    },
    hasOwnProperty: function (sKey) {
      return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
    }
  };
  window.localStorage.length = (document.cookie.match(/\=/g) || window.localStorage).length;
}

扩展 localStorage 支持 expires

(function () {
  var getItem = localStorage.getItem.bind(localStorage)
  var setItem = localStorage.setItem.bind(localStorage)
  var removeItem = localStorage.removeItem.bind(localStorage)
  localStorage.getItem = function (keyName) {
    var expires = getItem(keyName + '_expires')
    if (expires && new Date() > new Date(Number(expires))) {
      removeItem(keyName)
      removeItem(keyName + '_expires')
    }
    return getItem(keyName)
  }
  localStorage.setItem = function (keyName, keyValue, expires) {
    if (typeof expires !== 'undefined') {
      var expiresDate = new Date(expires).valueOf()
      setItem(keyName + '_expires', expiresDate)
    }
    return setItem(keyName, keyValue)
  }
})()

使用

localStorage.setItem('key', 'value', new Date() + 10000) // 10 秒钟后过期
localStorage.getItem('key')

@abshawn
Copy link

abshawn commented Jul 12, 2019

搞不懂啊

@J-DuYa
Copy link

J-DuYa commented Jul 12, 2019

localstorage本身没有时间过期的功能,要是自己封装一个简单的localstorage功能的话,可以使用定时器去实现过期时间的功能,值得注意的是执行定时器后,到指定的时间,记得destroy定时器。

export class mockLocalstorage {
  constructor() {
    this.store = new Map(); // 记录存储数据
  }

  getItem(key) {
    const stringKey = String(key);
    if (this.store.has(stringKey)) {
      return String(this.store.get(stringKey));
    } else {
      return null;
    }
  }

  // time单位是小时
  setItem(key, val, time = "undefined") {
    let stime = null;
    if (typeof time !== "number" && time !== "undefined") {
      throw new Error("设置过期时间的基础单位是小时,请不要破坏规则!");
    }

    if (time !== "undefined") {
      time = time * 60 * 60 * 1000; // h ---> ms
      try {
        let _this = this;
        this.store.set(String(key), val);
        // 设置定时器 定时清空垃圾数据
        stime = setTimeout(() => {
          _this.removeItem(key);
          stime = null;
        }, time);
      } catch (e) {
        stime = null;
        throw new Error(e);
      }
    }
  }

  keys() {
    return Object.keys(this.store);
  }

  removeItem(key) {
    this.store.delete(String(key));
  }

  clear() {
    this.store.clear();
  }

  get length() {
    return this.store.size;
  }
}

测试代码(vue代码)

let _this = this;
let time = null;
this.mockLocalstorage.setItem("name", "duya", 0.01);
console.log(this.mockLocalstorage.getItem("name")); // 测试mockLocalstorage duya
  
// 检测数据又没有清空😄
time = setTimeout(() => {
  time = null;
  console.log("name " + _this.mockLocalstorage.getItem("name")); // "name: null"
}, 0.01 * 60 * 60 * 1000);

@630268501
Copy link

感觉不能用定时器,因为浏览器会关闭。不知道我感觉的对不对

@iNuanfeng
Copy link

??存储的时候加个存储时间戳和有效期时长就好了啊。取的时候判断一下不就行了

@Yezzle
Copy link

Yezzle commented Jul 12, 2019

浏览器切换下一个页签定时器有可能被暂停

@niexia
Copy link

niexia commented Jul 12, 2019

使用定时器来做过期时间功能的话,重复设置某个值,如果第二次有效期比第一次长,那么到期之前,第一次已经将它清空了。
加个时间戳和有效期是不是合适一些?基于第 103 题方法的修改

class LocalStorage {
  constructor () {
    this.store = new Map();
    this.expires = new Map();
  }

  getItem(key) {
    const stringKey = String(key);
    if (this.store.has(stringKey)) {
      let time = this.expires.get(stringKey);
      if (!time) {
        return String(this.store.get(stringKey));
      } else if (Date.now() < (time[0] + time[1])) {
        return String(this.store.get(stringKey));
      } else {
        this.store.delete(stringKey);
        this.expires.delete(stringKey);
        return null;
      }
    }
    return null;
  }

  setItem(key, val, time) {
    this.store.set(String(key), String(val));
    time = Number(time);
    if (time && time >= 0) {
      this.expires.set(String(key), [Date.now(), time]);
    }
  }
...

@bee0060
Copy link

bee0060 commented Jul 13, 2019

定时器问题,每次set除了要创建新定时器,还要清空已存在的定时器,亦即每次set都会更新定时器,就不必担心旧的定时器清空新set的值了。上面的处理貌似都没在set的时候处理定时器。

更重要的, localStorage要刷新后依然存在。貌似只有cookie模拟的实现了这个特性。
只是可能有以下问题:

localStorage.setItem('myKey', 'value', new Date() + 10000) // 10 秒钟后过期

// 如果再设置以下key,可以让myKey永不过时
localStorage.setItem('myKey_expires', 'abc') 

// 或者设置以下key,可以让myKey立即过时
localStorage.setItem('myKey_expires', '') 

总之使用关键字的形式,可能会因为特殊key的设置导致其他某些key的过期功能失效或不按预期执行。 所以个人还是更支持用定时器的方式。

可能看起来有点吹毛求疵。 只是实际面试中,面试官看到你的答案后,可能会加问一句,“这个方案可能会有什么潜在问题。 ”

@habc0807
Copy link

参照楼下的做下总结,实现过期时间 两种常见的方案:
1.时间戳 可能会导致过期功能失效
2.定时器 切换页面时浏览器会自动延迟我们的定时器,以便于节约资源,可能会造成定时器时间不准确
这两种方案,各有优缺点,你会选择哪种

@keepsaunter
Copy link

		const localStorageMock = (function() {
			let store = {}
			return {
				getItem: function(key) { return store[key] || null },
				setItem: function(key, value, time) { // time 是毫秒级别,过时时间必须大于0ms
					time = Number(time)?time:0;
					store[key] = value.toString()
					if(time>0){this.timeOut(key,time)}
				},
				timeOut:function(key,time){
					let that = this;
					let timer = setTimeout(function(){ that.removeItem(key);clearTimeout(timer);},time)
				},
				removeItem: function(key) { delete store[key] },
				clear: function() { store = {} },
			}
		})()

		Object.defineProperty(window, 'localStorage2', {
			value: localStorageMock
		})
		
		
		localStorage2.setItem('test',"test",3000)
		console.log(localStorage2.getItem("test"))  //test
		
		setTimeout(function(){
			console.log(localStorage2.getItem("test"))  //null
		},4000);

setTimeout不能保证到时间点就执行吧

@Yxiuchao
Copy link

const localStorage = (function () {
  let store = {}
  return {
    getItem: function (key) {
      return store[key] || null
    },
    setItem: function (key, val, time) {
      time = Number(time) || 0;
      store[key] = val.toString();
      if (time > 0) {
        this.timeOut(key, time);
      }
    },
    timeOut: function (key, time) {
      var timer = setTimeout(() => {
        this.removeItem(key);
        clearTimeout(timer)
      }, time);
    },
    removeItem: function (key) {
      delete store[key]
    },
    clear: function () {
      store = {}
    }
  }
})()
Object.defineProperty(window, 'localStorage2', {
  value: localStorage
})

@Mini-Web
Copy link

class miniLocalStorage {
    static unSafeExpires = 2147483647;
    static Store = Object.create(null);
    static storeExpires = Object.create(null);

    constructor() {}

    getItem(key) {
        return miniLocalStorage.Store[key];
    }

    setItem(key, val, expires) {
        console.log('setItem', Date.now())
        try {
            if (typeof expires === 'number' && !Number.isNaN(expires)) {
                if (expires > miniLocalStorage.unSafeExpires) {
                    throw Error('expires overflow');
                }

                this._clearTimeout(key)

                miniLocalStorage.storeExpires[key + '_timer'] = setTimeout(()=>{
                    delete miniLocalStorage.Store[key]
                }
                , expires);
            }

            miniLocalStorage.Store[key] = val
        } catch (e) {
           throw e;
        }
    }

    removeItem(key) {
        this._clearTimeout(key)
        delete miniLocalStorage.Store[key];
    }

    clear() {
        for (const key in miniLocalStorage.Store) {
            this.removeItem(key)
        }
    }

    _clearTimeout(key) {
        const expiresId = key + '_timer'

        if (expiresId in miniLocalStorage.storeExpires) {
            clearTimeout(miniLocalStorage.storeExpires[expiresId])
            delete miniLocalStorage.storeExpires[expiresId];
        }
    }
}

var miniLocalStorage2 = (function() {
    const Store =  Object.create(null);

    function miniLocalStorage2() {}

    miniLocalStorage2.prototype.getItem = function getItem(key) {
        if (Store[key]) {
            if ('expires' in Store[key] && Date.now() > Store[key].expires) { 
                delete Store[key]
            } else {
                return Store[key].value;
            }
        }
    }

    miniLocalStorage2.prototype.setItem = function setItem(key, value, expires) {
        Store[key] = {
            value
        };
        console.log('setItem', Date.now())

        if (typeof expires === 'number' && !Number.isNaN(expires)) {
            Store[key].expires = (Date.now() + expires);
        }
    }

    miniLocalStorage2.prototype.removeItem = function removeItem(key) {
        delete Store[key];
    }

    miniLocalStorage2.prototype.clear = function clear() {
        for (const key in Store) {
            this.removeItem(key)
        }
    }

    return miniLocalStorage2;
}
)()

@wangliang1124
Copy link

localStorage最主要的能力是本地存储,即使关闭浏览器还是存在的,因此基于cookie做个模拟

function setCookie(key, value = '', days) {
        if (!key) return
        let date = new Date()
        date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); // 设置过期时间为 days 天之后
        document.cookie = key + "=" + decodeURIComponent(value) + ";expires=" + date.toGMTString();
    }

    function getCookie(key) {
        let result = ''
        let cookie = document.cookie + ";"
        let queryString = key + "="
        let start = cookie.indexOf(queryString)
        if (start > -1) {
            start += queryString.length
            let end = cookie.indexOf(";", start)
            result = (cookie.substring(start, end))
        }
        // return getCookies()[key]
        return result
    }

    function getCookies() {
        let result = {}
        if (!document.cookie) return result
        document.cookie.split(';').forEach((item) => {
            const [key, value] = item.split('=')
            result[key.trim()] = value.trim()
        })
        return result
    }

    window.localStorage = window.localStorage || new LocalStorage()

    function LocalStorage() {
        const cookies = getCookies()
        this.length = Object.keys(cookies).length
        for (let [key, value] of Object.entries(cookies)) {
            this[key] = value
        }
    }

    LocalStorage.prototype = function () {
        function getItem(key) {
            return getCookie(key);
        }

        function setItem(key, value, days) {
            this[key] = value
            this.length++
            setCookie(key, value, days)
        }

        function removeItem(key) {
            this.length--
            delete this[key]
            setItem(key, '', -1);
        }

        function clear() {
            const cookies = getCookies();
            for (let key of Object.keys(cookies)) {
                delete this[key]
                removeItem(key);
            }
            this.length = 0
        }

        return {
            constructor: LocalStorage,
            getItem,
            setItem,
            removeItem,
            clear,
        }
    }()

@willove
Copy link

willove commented Jul 19, 2019

其实只需要在获取数据的时候判断数据的时间是否已经过期就好了,最多再加一个time interval 去定时遍历数据的过期时间。

@ScholatLouis
Copy link

在set key的时候 加上一个timestamp
获取的时候拿这个timestampe进行时间判断 看是否过期

@wangpengfeido
Copy link

wangpengfeido commented Jul 29, 2019

随便写了一个,麻烦大家看一下有没有不对。setItem的时候同时保存过期时间戳,同时可以根据需要是否自行定时检查。主要写一下思路,没有做过多的值的合法检查,关于值的合法检查的问题就不要批评我了。

class ExpirableLocalStorage {
  /**
   * 设置localStorage键值对
   * @param {Integer} expires 过期时间戳
   */
  static setItem(key, value, expires) {
    if (expires < Date.now()) {
      this.removeItem(key);
      return;
    }
    window.localStorage.setItem(key, value);
    window.localStorage.setItem(`expires_${key}`, expires);
  }

  /**
   * 获取localStorage值
   */
  static getItem(key) {
    if (parseInt(window.localStorage.getItem(`expires_${key}`)) < Date.now()) {
      this.removeItem(key);
      return null;
    }
    return window.localStorage.getItem(key);
  }

  /**
   * 移除localStorage键值对
   */
  static removeItem(key) {
    window.localStorage.removeItem(key);
    window.localStorage.removeItem(`expires_${key}`);
  }

  /**
   * 检查过期的键值对并移除
   */
  static checkExpired() {
    for (let i = 0, len = window.localStorage.length; i < len; i++) {
      const key = window.localStorage.key(i);
      const value = window.localStorage.getItem(key);
      const expires = window.localStorage.getItem(`expires_${key}`);
      if (value && expires && expires < Date.now()) {
        this.removeItem(key);
      }
    }
  }

  /**
   * 定时检查
   */
  static timingCheck(interval = 1000) {
    if (ExpirableLocalStorage.timer) {
      clearInterval(ExpirableLocalStorage.timer);
    }
    ExpirableLocalStorage.timer = setInter(() => {
      this.checkExpired();
    }, interval);
  }
}

@wulichenyang
Copy link

用 cookie 模拟 localStorage

参考 https://developer.mozilla.org/zh-CN/docs/Web/API/Storage/LocalStorage

if (!window.localStorage) {
  window.localStorage = {
    getItem: function (sKey) {
      if (!sKey || !this.hasOwnProperty(sKey)) { return null; }
      return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1"));
    },
    key: function (nKeyId) {
      return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "").split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]);
    },
    setItem: function (sKey, sValue) {
      if(!sKey) { return; }
      document.cookie = escape(sKey) + "=" + escape(sValue) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/";
      this.length = document.cookie.match(/\=/g).length;
    },
    length: 0,
    removeItem: function (sKey) {
      if (!sKey || !this.hasOwnProperty(sKey)) { return; }
      document.cookie = escape(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
      this.length--;
    },
    hasOwnProperty: function (sKey) {
      return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
    }
  };
  window.localStorage.length = (document.cookie.match(/\=/g) || window.localStorage).length;
}

扩展 localStorage 支持 expires

(function () {
  var getItem = localStorage.getItem.bind(localStorage)
  var setItem = localStorage.setItem.bind(localStorage)
  var removeItem = localStorage.removeItem.bind(localStorage)
  localStorage.getItem = function (keyName) {
    var expires = getItem(keyName + '_expires')
    if (expires && new Date() > new Date(Number(expires))) {
      removeItem(keyName)
      removeItem(keyName + '_expires')
    }
    return getItem(keyName)
  }
  localStorage.setItem = function (keyName, keyValue, expires) {
    if (typeof expires !== 'undefined') {
      var expiresDate = new Date(expires).valueOf()
      setItem(keyName + '_expires', expiresDate)
    }
    return setItem(keyName, keyValue)
  }
})()

使用

localStorage.setItem('key', 'value', new Date() + 10000) // 10 秒钟后过期
localStorage.getItem('key')

使用方法好像expire参数需要修改一下:
localStorage.setItem('key', 'value', new Date(Date.now() + 10000)) // 10 秒钟后过期

@pagemarks
Copy link

模拟cookie功能,之前写了个lib: https://github.com/pagemarks/codes/blob/master/js/cache.js

function getStorage(isSession) {
    return isSession ? sessionStorage : localStorage;
}

const cache = {
    get(key, isSession = true, val = null) {
        const storage = getStorage(isSession);
        let ret = storage.getItem(key);
        if (!ret && val !== null) return val; //default val
        const char = ret && ret.slice(0, 1);
        if (char && (char === '{' || char === '[')) {
            ret = JSON.parse(ret);
            if (ret.expires) {
                if (ret.expires >= Date.now()) {
                    if ('value' in ret && Object.keys(ret).length === 2) {
                        ret = ret.value;
                    } else {
                        delete ret.expires;
                    }
                } else {
                    ret = val !== null ? val : null;
                    this.del(key, isSession)
                }
            }
        } else if (ret === 'true' || ret=== 'false') {
            ret = ret === 'true';
        }
        return ret;
    },
    hget(key, hash, isSession = true) {
        return this.get(key, isSession, {})[hash];
    },
    set(key, value, isSession = true, seconds = 0) {
        const storage = getStorage(isSession);
        let val = value;
        if (seconds) {
            const expires = Date.now() + seconds;
            val = Object.assign({}, typeof value === 'object' ? value : { value }, { expires });
        }
        if (typeof value === 'object') val = JSON.stringify(val);
        storage.setItem(key, val);
    },
    hset(key, hash, val, isSession = true) {
        const ob = this.get(key, isSession, {});
        ob[hash] = val;
        this.set(key, ob, isSession);
    },
    del(key, isSession = true) {
        const storage = getStorage(isSession);
        storage.removeItem(key);
    }
};
export default cache;

@vaynevayne
Copy link

cookie 只能存那么一点东西, 你有cookie模拟意义在哪里

@vaynevayne
Copy link

// Custom storage object
const storage: StateStorage = {
  getItem(key) {
    const itemStr = localStorage.getItem(key)

    // if the item doesn't exist, return null
    if (!itemStr) {
      return null
    }

    const item = JSON.parse(itemStr)
    const now = new Date()

    // compare the expiry time of the item with the current time
    if (now.getTime() > item.expiry) {
      // If the item is expired, delete the item from storage
      // and return null
      localStorage.removeItem(key)
      return null
    }
    return item.value
  },

  setItem(key, value) {
    const now = new Date()

    // `item` is an object which contains the original value
    // as well as the time when it's supposed to expire
    const item = {
      value: value,
      expiry: now.getTime() + 7 * 24 * 60 * 60 * 1000, //  7 days
    }
    localStorage.setItem(key, JSON.stringify(item))
  },

  // remove the entry with the given key
  removeItem(key) {
    window.localStorage.removeItem(key)
  },
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests