// 遍历所有的校验信息进行验证
for (let [key, type] of Object.entries(validateConf)) {
if (typeof this[key] !== type) throw new Error(`${key} must be ${type}`)
}
}
}
}
function validate(type) {
return function (target, name, descriptor) {
// 向全局对象中传入要校验的属性名及类型
validateConf[name] = type
}
}
new Person('Niko', '18') // throw new error: [age must be number]
首先,在类上边添加装饰器@validator,然后在需要校验的两个参数上添加@validate装饰器,两个装饰器用来向一个全局对象传入信息,来记录哪些属性是需要进行校验的。
然后在validator中继承原有的类对象,并在实例化之后遍历刚才设置的所有校验信息进行验证,如果发现有类型错误的,直接抛出异常。
这个类型验证的操作对于原Class来说几乎是无感知的。
函数参数装饰器
最后,还有一个用于函数参数的装饰器,这个装饰器也是像实例属性一样的,没有办法单独使用,毕竟函数是在运行时调用的,而无论是何种装饰器,都是在声明类时(可以认为是伪编译期)调用的。
函数参数装饰器会接收三个参数:
1.类似上述的操作,类的原型或者类的构造函数
2.参数所处的函数名称
3.参数在函数中形参中的位置(函数签名中的第几个参数)
一个简单的示例,我们可以结合着函数装饰器来完成对函数参数的类型转换:
const parseConf = {}
class Modal {
@parseFunc
addOne(@parse('number') num) {
return num + 1
}
}
// 在函数调用前执行格式化操作
function parseFunc (target, name, descriptor) {
return {
...descriptor,
value (...arg) {
// 获取格式化配置
for (let [index, type] of parseConf) {
switch (type) {
case 'number': arg[index] = Number(arg[index]) break
case 'string': arg[index] = String(arg[index]) break
case 'boolean': arg[index] = String(arg[index]) === 'true' break
}
return descriptor.value.apply(this, arg)
}
}
}
}
// 向全局对象中添加对应的格式化信息
function parse(type) {
return function (target, name, index) {
parseConf[index] = type
}
}
console.log(new Modal().addOne('10')) // 11
使用装饰器实现一个有趣的Koa封装
比如在写Node接口时,可能是用的koa或者express,一般来说可能要处理很多的请求参数,有来自headers的,有来自body的,甚至有来自query、cookie的。
所以很有可能在router的开头数行都是这样的操作:
router.get('/', async (ctx, next) => {
let id = ctx.query.id
let uid = ctx.cookies.get('uid')
let device = ctx.header['device']
})
以及如果我们有大量的接口,可能就会有大量的router.get、router.post。
以及如果要针对模块进行分类,可能还会有大量的new Router的操作。
这些代码都是与业务逻辑本身无关的,所以我们应该尽可能的简化这些代码的占比,而使用装饰器就能够帮助我们达到这个目的。
装饰器的准备
// 首先,我们要创建几个用来存储信息的全局List
export const routerList = []
export const controllerList = []
export const parseList = []
export const paramList = []
// 虽说我们要有一个能够创建Router实例的装饰器
// 但是并不会直接去创建,而是在装饰器执行的时候进行一次注册
export function Router(basename = '') {
return (constrcutor) => {
routerList.push({
constrcutor,
basename
})
}
}
// 然后我们在创建对应的Get Post请求监听的装饰器
// 同样的,我们并不打算去修改他的任何属性,只是为了获取函数的引用
export function Method(type) {
return (path) => (target, name, descriptor) => {
controllerList.push({
target,
type,
path,
method: name,
controller: descriptor.value
})
}
}