写前端和后端的时候,往往要进行数据校验。
例如:
后端在处理数据之前,需要校验前端传过来的数据和后端要求的数据是否相符,以及是否存在超限问题(大于最大值或者小于最小值,或者字符串长度过长过短)。
在常规做法中,我们需要写多个函数,然后一个一个的校验过去,十分麻烦。
如果某个属性要在多个地方验证,并且验证条件是统一的,那么有两种写法:
比较笨是把一个写好的复制到另外一个地方,但若需求变更,那么修改的时候就很麻烦了;比较聪明的是通过原型链来继承这个验证函数,不过这样也很麻烦。当验证函数比较多的时候,整理起来也比较麻烦,而且不容易观察到有哪些地方引用了。第二种方法已经算是比较聪明的了,但还有一个缺点,那么就是需要专门调用,不够智能。
如果我们能在赋值的时候自动校验一下是否符合要求,不符合要求就不赋值,那么就很方便了。
对于这种要求,在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(比如只允许读或只允许写)