本篇文章参考书籍《JavaScript设计模式》–张容铭
前言
各位作为一名前端开发,大家有没有遇到过前端存储,比如用户初次进入程序,会有一个个性化引导,一般来说这块功能是交给后台同事来做的,但是有时候需要放在前端,来保存是否第一次登陆的字段。
那我们在本地存储的时候呢,可能会遇到很多问题,每添加一组数据,担心会不会覆盖别人的数据等等,本节的设计模式,就介绍怎么合理前端存储。
数据访问对象模式
抽象和封装对数据源的访问与存储, DAO通过对数据源链接的管理方便对数据的访问与储存。
我们可以创建一个数据访问对象类,来解决上面的问题。这个数据访问对象类是对本地存储的一次封装,要创建 DAO 类,首先要了解当前的需求,最好也顾虑一下其他人的需求,这样创建的 DAO 类才更加健全。
首先,我们的数据需要有增,删,改,查的功能,其次当别人使用本地存储的时候,也需要知道我保存了哪些数据。
本地存储使用的是 localStorage 这个对象,对于同一个站点,里面根本没有分割库,所以所有人用的 localStorage 都是同一个,所以应该将每次存储的数据前面字段前面添加字段标识来分割 localStorage 库,
本地存储对数据的保存实际上是 localStorage 的一个字符串属性,有时对于存储来说,了解它的时间很重要,能方便日后的管理,因此我们还需要一个时间戳。
/**
* 本地存储类
* 参数 preId 本地存储数据库前缀
* 参数 timeSign 时间戳与数据之间的拼接符
*/
var BaseLocalStorage = function(preId, timeSign) {
//定义本地存储数据库前缀
this.preId = preId;
//定义时间戳与数据之间的拼接符
this.timeSign = timeSign || '|-|';
}
为了知道数据的状态,我们可以在 DAO 类内部保存操作返回状态供肉厚使用时调用,我们还需要将本地存储服务的引用保存在 DAO 类的内部以方便我们使用。当然 DAO 类还需要提供对数据库的增删改查操作接口方法。
//本地存储类原型方法
BaseLocalStorage.prototype = {
//操作状态
status: {
SUCCESS: 0, //成功
FALLURE: 1, //失败
OVERFLOW: 2, //溢出
TIMEOUT: 3, //过期
},
//保存本地存储链接
stroge: localStorage || window.localStorage,
//获取本地存储数据库真实字段
getKey: function(key) {
return this.preId + key;
},
//添加(修改)数据
set: function(key, value, callback, time) {
//省略操作
},
//获取数据
get: function(key, callback) {
//省略操作
},
//删除数据
remove: function(key, callback) {
//省略操作
}
}
我们下面的事,只要实现 set , get , remove 就可以了。首先是 set 方法,我们需要在字段中添加时间戳,我们向本地存储中添加的数据实质上事调用 localStorage 的 setItem 方法。最后我们还要执行回调函数并将操作的结果传入回调函数中。
/**
* 添加(修改)数据
* 参数 key 数据字段标识
* 参数 value 数据值
* 参数 callback 回调函数
* 参数 time 添加时间
*/
set: function(key, value, callback, 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.OVERFKOW;
}
//有回调函数则执行回调函数,并传入参数操作状态,真实数据字段标识以及存储数据值
callback && callback.call(this, status, key, calue);
}
有了设置数据方法,通过存储字段获取数据的方法也就简单了,还是调用 localStorage 的 getItem 方法,不过需要注意是,这里兼容了四种查询数据的情况,第一种,该字段数据本来就不存在,这样应该返回失败状态;第二种,操作成功但没有获取到值,此时也应该返回失败;第三种,获取到值了,但是时间已经过期了,此时应该删除该数据;最后一种,成功获取数据并成功返回。
/**
* 获取数据
* 参数 key 数据字段标识
* 参数 callback 回调函数
*/
get: function(key, callback) {
//默认操作状态时成功
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) {
//获取失败则返回失败状态,数据结果为null
result = {
status: that.status.FAILURE,
value: null
};
//执行回调并返回
callback && callback.call(this, result.status, result.value);
return result;
}
//如果成功获取数据字符串
if(value) {
//获取时间戳与存储数据之间的拼接符起始位置
index = value.indexOf(that.timeSign);
//获取时间戳
time = +value.slice(0, index);
//如果时间为过期
if(new Date(tiem).getTime() > newDate.getTime() || time == 0) {
//获取数据结果(拼接符后面的字符串)
value = value.slice(index + timeSignLen);
} else {
//过期则结果为null
value = null;
//设置状态为过期状态
status = that.status.TIMEOUT;
//删除该字段
that.remove(key);
}
} else {
//未获取数据字符串状态为失败状态
status = that.status.FAILURE;
}
//设置结果
result = {
status: status,
value: value
};
//执行回调函数
callback && cakkback.call(this, result.status, result.value);
//返回结果
return result;
}
remove 方法照猫画虎,首先删除应该用 localStorage 的 removeItem 方法。执行时会有 3 中状态,第一种,没有修改字段,这样操作结果是失败状态,第二种情况是删除(调用 removeItem 方法) 时发生异常,这样也应该是失败,最后一种当然就是修改成功。
/**
* 删除数据
* 参数 key 数据字段标识
* 参数 callback 回调函数
*/
remove: function(key, callback) {
//设置默认操作状态为失败
var status = this.status.FAILURE,
//获取实际数据字段名称
key = this.getKey(key),
//设置默认数据结果为空
value = null;
try {
//获取字段对应的数据
value = this.storage.getItem(key);
} catch (e) {}
//如果数据存在
if(value) {
try {
//删除数据
this.storage.removeItem(key);
//设置操作成功
status = this.status.SUCCESS;
} catch (e) {}
}
//执行回调,注意传入回调函数中的数据值:如果操作状态成功则返回真实的数据结果,否则返回空
callback && callback.call(this, status, status > 0 ? null : value.slice(value.indexOf(this.timeSign) + this.timeSign.length))
}
这样我们的整个 DAO类 就完成了,接下来测试一下。上面有一个小技巧不知道大家发现没有,我们在设置默认状态的时候, set 和 get 默认操作是成功,而 remove 的操作状态却是失败,这是为什么的?
这是因为我们在设置默认状态的时候,应该保证默认状态尽可能多的出现,这样我们的代码执行效率会高一些,这也体现了一个工程师对自己设计的代码的一个全局把控能力。
var LS = new BaseLocalStorage('LS__');
LS.set('a', 'xiao ming', function() {console.log(arguments);}); //[0, "xiao ming", "LS__a", "xiao ming"]
LS.get('a', function() {console.log(arguments);}); //[0, "xiao ming"]
LS.remove('a', function() {console.log(arguments);}); //[0, "xiao ming"]
LS.remove('a', function() {console.log(arguments);}); //[1, null]
LS.get('a', function() {console.log(arguments);}); //[1, null]
对于不支持本地存储的浏览器,我们有时会通过 cookie 或者浏览器 userData 存储区存储数据,这就需要对我们的 DAO 进行重构,兼容更多浏览器,重构方法可以用我们之前学习到的设计模式,各位就看你们的掌握程度了。具体大家可以当作一个练习题来做。