本文主要解决原生localStorage无法设置过期时间的问题,并通过封装,来实现一个操作便捷,功能强大的localStorage库,关于库封装的一些基本思路和模式,我将采用之前写的如何用不到200行代码写一款属于自己的js类库中类似的方法,感兴趣的朋友可以学习,交流。
设计思路
我们将基于localStorage原始api进行扩展,让其支持失效时间,操作完成后的回调。在文章的最后,我将给出库的完成代码,接下来我们就一步步实现吧。
正文
首先,我们来设计库的基本框架:
- const BaseStorage = function(preId, timeSign){
- // 初始化一些操作
- }
- BaseStorage.prototype = {
- storage: localStorage || window.localStorage,
- set: function(key, value, cb, time){
- },
- get: function(key, cb){
- },
- // 删除storage,如果删除成功,返回删除的内容
- remove: function(key, cb){
- }
- }
如上可以发现,我们的storage会有三个核心api,分别为set,get,remove,我们使用localStorage作为基础库支持,当然你也可以将上面的库换成sessionStorage或者其他。
有了基本骨架,我们就可以实现基本功能的封装,这里我们先在原型中加一个属性,来列出数据操作中的各个状态。
- status: {
- SUCCESS: 0, // 成功
- FAILURE: 1, // 失败
- OVERFLOW: 2, // 数据溢出
- TIMEOUT: 3 // 超时
- },
为了实现过期时间,我们有两种思路,第一种是先将一个过期时间存到storage中,每次操作都检查一遍是否过期,但是这种方案意味着对不同的键就要设置不同的过期时间的storage与之对应,这样会占用额外的库内存,维护起来也不方便。另一种方法就是将过期时间存放到键值中,将时间和值通过标识符分隔,每次取的时候从值中截取过期时间,再将真实的值取出来返回,这种方案不会添加额外的键值对存储,维护起来也相对简单,所以我们采用这种方案。 为了区分不同的库对象,我们还可以添加键前缀,如下:
- const BaseLocalStorage = function(preId, timeSign){
- this.preId = preId; // 键前缀
- this.timeSign = timeSign || '|-|'; // 过期时间和值的分隔符
- }
基于这个思想,我们就可以接下来的实现了。
getKey——修饰key的方法,不影响用户对真实key的影响
- getKey: function(key){
- return this.preId + key
- },
set实现
- set: function(key, value, cb, time){
- var status = this.status.SUCCESS,
- key = this.getKey(key);
- // 设置失效时间,未设置时间默认为一个月
- try{
- time = new Date(time).getTime() || time.getTime();
- }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实现
- get: function(key, cb){
- var 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
- }
remove实现
- // 删除storage,如果删除成功,返回删除的内容
- remove: function(key, cb){
- var status = this.status.FAILURE,
- key = this.getKey(key),
- value = null;
- try{
- value = this.storage.getItem(key);
- }catch(e){
- // dosomething
- }
- if(value){
- try{
- this.storage.removeItem(key);
- status = this.status.SUCCESS;
- }catch(e){
- // dosomething
- }
- }
- cb && cb.call(this, status, status > 0 ? null : value.slice(value.indexOf(this.timeSign) + this.timeSign.length))
- }
在api的实现过程中,由于某种误操作很可能导致storage报错,所以建议最好用trycatch包裹,这样可以避免影响后面的逻辑。
接下来我们可以这么使用:
- let a = new BaseStorage('_', '@');
- a.set('name', '123')
- a.get('name') // {status: 0, value: "123"}
- // 设置失效时间
- a.set('name', '123', null, new Date().getTime() + 1000*60*60*24*31)
- // 移除
- a.remove('name')
完整源码
- /**
- * 数据管理器
- */
- (function(win){
- const BaseStorage = function(preId, timeSign){
- this.preId = preId;
- this.timeSign = timeSign || '|-|';
- }
- BaseStorage.prototype = {
- status: {
- SUCCESS: 0,
- FAILURE: 1,
- OVERFLOW: 2,
- TIMEOUT: 3
- },
- storage: localStorage || window.localStorage,
- getKey: function(key){
- return this.preId + key
- },
- set: function(key, value, cb, time){
- var status = this.status.SUCCESS,
- key = this.getKey(key);
- // 设置失效时间,未设置时间默认为一个月
- try{
- time = new Date(time).getTime() || time.getTime();
- }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, cb){
- var 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
- },
- // 删除storage,如果删除成功,返回删除的内容
- remove: function(key, cb){
- var status = this.status.FAILURE,
- key = this.getKey(key),
- value = null;
- try{
- value = this.storage.getItem(key);
- }catch(e){
- // dosomething
- }
- if(value){
- try{
- this.storage.removeItem(key);
- status = this.status.SUCCESS;
- }catch(e){
- // dosomething
- }
- }
- cb && cb.call(this, status, status > 0 ? null : value.slice(value.indexOf(this.timeSign) + this.timeSign.length))
- }
- }
- win.BS = BaseStorage;
- })(window)