利用setter和getter实现数据校验
写前端和后端的时候,往往要进行数据校验。
例如:
后端在处理数据之前,需要校验前端传过来的数据和后端要求的数据是否相符,以及是否存在超限问题(大于最大值或者小于最小值,或者字符串长度过长过短)。
在常规做法中,我们需要写多个函数,然后一个一个的校验过去,十分麻烦。
如果某个属性要在多个地方验证,并且验证条件是统一的,那么有两种写法:
- 比较笨是把一个写好的复制到另外一个地方,但若需求变更,那么修改的时候就很麻烦了;
- 比较聪明的是通过原型链来继承这个验证函数,不过这样也很麻烦。当验证函数比较多的时候,整理起来也比较麻烦,而且不容易观察到有哪些地方引用了。
第二种方法已经算是比较聪明的了,但还有一个缺点,那么就是需要专门调用,不够智能。
如果我们能在赋值的时候自动校验一下是否符合要求,不符合要求就不赋值,那么就很方便了。
对于这种要求,在es5的setter和getter特性出来后,是可以实现的。
在setter的时候,校验一下数据类型是否符合,如果符合则赋值,不符合则直接返回即可。如果有必要,还可以记录一下,甚至控制台打印报错信息。
这里给一个示例代码:
class Foo {
constructor(val) {
this._errorList = []
// 遍历作为构造函数传的参数的每个属性,然后赋值之
for (let i in val) {
this[i] = val[i]
}
}
get age() {
return this._age ? this._age : null
}
set age(val) {
// 赋值前进行数据验证
if (val >= 20 && val <= 100) {
this._age = val
} else {
this.errorList.push('age')
}
}
// 直接查看值可以得知赋值过程中是否有错
get isDataError() {
if (this.errorList.length === 0) {
return false
} else {
return true
}
}
// 获取错误列表
get errorList() {
return this._errorList
}
// 赋值的时候,只是将错误添加到错误列表里(简化示例,不考虑重置)
set errorList(val) {
this._errorList.push(val)
}
// 获取当前赋值的数据
getData() {
return {
age: this.age
}
}
}
let p1 = new Foo({age: 1})
if (p1.isDataError) {
console.log(p1.errorList)
} else {
console.log(p1.getData())
}
// ["age"]
let p2 = new Foo({age: 30})
if (p2.isDataError) {
console.log(p2.errorList)
} else {
console.log(p2.getData())
}
// {age: 30}
let p3 = new Foo()
if (p3.isDataError) {
console.log(p3.errorList)
} else {
console.log(p3.getData())
}
// {age: null}
以上代码包括:
- 赋值检测;
- 取值(返回有效值或默认值);
- 错误检查(
isDataError
); - 正确则返回结果(
getData()
); - 错误则返回错误列表(
errorList()
);
基本可以应对一般性的需求了。
另外提一下关于代码复用时的思路:
- 对于需要复用的属性,可以写一个基类,然后其他需要他的继承该基类就行;
- 基类的校验函数如果亢余,并不是问题,因为通常不会被调用;
- 如果像上面Foo类的构造函数这样批量赋值,那么错误校验的逻辑不应该放在setter里,而是应该放在getter里(取值时校验报错),返回错误后应该清空之前的错误列表;
- 对于不同的类,拿取数据的时候,应该用一个专门的函数(如
getData()
)负责拿数据,这样稳妥一点。而对于基类,应该只负责数据校验,而不负责取数据; - 如果校验的类比较多的情况下,应该用专门的js文件存放这些类,然后导出他们;
- 如果再更多一点的话,应该用一个专门的文件夹,然后不同的js存放不同页面/逻辑/功能模块的校验类,视需求引入;
- 总而言之,是抽出校验逻辑,解耦他,方便维护和开发;
最后附一个关于setter和getter用法和优点的总结:
引自《 API design for C++ 》
- 有效性验证(可以在setter里检查设置的值是否在许可区间里)
- 惰性求值(比如一个成员计算过于耗时,而这个类的用户(这里的用户指其他程序员)不一定需要时,可以在getter方法调用的时候再计算)
- 缓存额外的操作(比如用户调用setter方法时,可以把这个值更新到配置文件里)
- 通知(其它模块可能需要在某个值发生变化的时候做一些操作,那么就可以在setter里实现)
- 调试(可以方便的打印设置日志,从而追踪错误)
- 同步(如果多线程访问需要加锁的话,setter里加锁不是很容易么)
- 更精细的权限访问(比如private变量只有getter没有setter,那客户对该变量就是只读了,而类的内部代码可以读写)
- 维护不变式关系(比如一个类内部要维持连个变量a和b有a = b * 2的关系,那么在a和b的setter里计算就能维持这样的关系)
以上引用内容复制自:
作者:浅墨
链接:https://www.zhihu.com/question/21401198/answer/37192335
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
对于js来说,有用的有:
- 常见情况下:1(超限则赋值无效)、5(很好用的说)、8(比如改了当前值后,顺便改了和他联动的另外一个值)
- 不常见情况有:2(类似的效果可以参考Vue的计算属性、4(比如关键属性被修改,log一发)、7(比如只允许读或只允许写)