装饰器概念
装饰器是面向对象的概念(在java中叫做注解,早c#中叫做特征,英文名字是decorator
在angular大量使用,react中也会用到
JS支持装饰器,目前处于建议征集的第二阶段,翻阅网上的大量文章,发现17年的时候就开始有装饰器了,也一直都在意见征集阶段,直到现在也是一样的。ts 中需要使用这个需要在 配置文件ts.config.json 中开启 experimentalDecorators: true就能使用装饰器了。
装饰器的作用
看下面的例子中:
class A { constructor( private prop1: string, private prop2: number, private prop3: boolean, private prop4: string, private prop5: number, private prop6: boolean, // ... 这里还有有100个属性 ){} }
我们现在要做一个事情,对上面的每一个属性进行验证。按照现在的ts 的方式,我们可以这么做:
class A { constructor( private prop1: string, private prop2: number, private prop3: boolean, private prop4: string, private prop5: number, private prop6: boolean, // ... 这里还有有100个属性 ){} /** *验证函数 */ public validate(){ if(!this.prop1 && this.prop1.length < 10){ // 验证prop1 } if (this.prop2 > 0) { } if (!this.prop3) { } // ... } }
看上面的验证代码,我们可以发现一些不合理的地方,
- 每一个属性都要验证,我们就要写一个if 来进行判断,100个属性就要写100遍,然后每一个if中的条件还有可能相同,就会导致我们写重复的代码,这是非常麻烦的。
- 我们在写一个类的属性的时候,才是最清楚这个成员的条条框框,过了一会儿在写,可能都不记得。换一句话说是,类的成员与成员验证分开是不易于代码的维护的。
解决的问题
装饰器,能够带来额外的信息量,可以达到分离关注点的目的。
- 信息书写位置的问题
- 重复代码的问题
上述两个问题产生的根源:某些信息,在定义时,能够附加的信息量有限。
装饰器的作用:为某些属性、类、参数、方法提供元数据信息(metadata)
元数据:描述数据的数据
基于上面的这些因素,js 就提出了 装饰器的概念(现在依旧还没有正式发布),有了装饰器后,让前端的js 类的代码更像java 等后端语言了。但是我们要清楚的是,装饰器是js 中提出的,并不是ts 中的特殊语法。
原理本质
本质
在JS中,装饰器是一个函数。(装饰器是要参与运行的)
装饰器可以修饰:
- 类
- 成员(属性+方法)
- 参数
使用方法:
使用装饰器@装饰器函数
/** * 不为空判断 * @param target * @param prop */ function NotNull(target: any, prop: string) { // 判断静态属性 if (target.prototype && !target[prop]) { console.log(`${prop} 不能为空`) }else{ // 判断实例属性,这个需要使用工厂函数来实现动态创建类 if (!new (A as any)()[prop]) { console.log(`${prop} 不能为空`) } } } class A { @NotNull private prop1: string = '' @NotNull private prop2: number = 1; @NotNull static prop3: boolean = false; }
效果:
原理
上面的代码我们可以使用es5的代码来写出来进行分析:
function A(){} A.prototype.prop1 = ''; __decorator([NotNull],A.prototype,'prop1'); A.prototype.prop2 = 1; // 这里传入的A.prototype稍后解释 __decorator([NotNull],A.prototype,'prop2'); // 静态属性 A.prop3 = false; // 这里传入的A 稍后解释 __decorator([NotNull],A,'prop3 '); /** *不为空的函数 */ function NotNull(target, prop){ if(!target[prop]){ console.log(`${prop} 这个属性不能为空`) } } /** *装饰者函数, 这个是自己根据网上的文档进行理解出来的。 */ function __decorator (decorators, target, prop){ for(var i = 0; i < decorators.length; i ++){ decorators[i](target, prop) } }
效果:
通过上面的代码简写,我们可以得出以下结论(装饰器在ts中的前提)
- 装饰器的执行时间是在 属性 定义好后就直接执行的
- 装饰器就是一个函数,在ts 中会生成额外的代码
- 装饰器可以做到面向切面编程(AOP),上面的代码不影响,是直接在定义就完成了验证。
装饰器的使用方式
类中使用装饰器
类装饰器的本质是一个函数,该函数接收一个参数(必须),表示类本身(构造函数本身)
使用装饰器@得到一个函数
在TS中,如何约束一个变量为类
- Function
- new (参数)=>object
装饰器函数的运行时间:在类定义后直接运行
类装饰器可以具有的返回值:
- void:仅运行函数
- 返回一个新的类:会将新的类替换掉装饰目标
- 也可以在里面实现伪继承,但是不建议这么做(下面的列子是装饰器工厂)
- 多个装饰器的情况:会按照后加入先调用的顺序进行调用。
成员装饰器
- 属性
属性装饰器也是一个函数,该函数需要两个参数:
1.如果是静态属性,则为类本身;如果是实例属性,则为类的原型;
2.固定为一个字符串,表示属性名
注意: 这里的测试需要ts 编译的结果是es6以上,不然A和A的原型都是undefined,得到的结果会是错误的。换一句话说,需要将 ts.config.json 的 target: 'es6' ,因为es5是没有类的,打印A和A的原型会报错哦
- 方法
方法装饰器也是一个函数,该函数需要三个参数:
1.如果是静态方法,则为类本身;如果是实例方法,则为类的原型;
2.固定为一个字符串,表示方法名
3.属性描述对象
成员也可以可以有多个装饰器修饰,和类装饰器使用的方式一样,也可以使用装饰器工厂
参数中使用装饰器
要求函数有三个参数:
1.如果方法是静态的,则为类本身;如果方法是实例方法,则为类的原型
2.方法名称或者undefined
3.在参数列表中的索引
使用第三方库
元数据基础库 reflect-metadata
npm install reflect-metadata -S 这个库是用于保存元数据的初始值,这个是需要安装在生成环境的,因为装饰器是需要在代码运行时候起作用的。在 ts.config.json中加入配置emitDecoratorMetadata: true就会在编译结果中加入元数据。则TS在编译结果中,会将约束的类型,作为元数据加入到相应位置。这样一来,TS的类型检查(约束)将有机会在运行时进行。
使用方式:
看文档
类的验证库class-validator
npm install class-validator --save 这个库是对类进行验证的,使用方式,
看文档
把普通对象转类 class-transformer
npm install class-transformer --save 这个库是用于把一维的对象转成类,使用方式
看文档
上面的这两个库都需要依赖于 reflect-metadata (元数据的基础库)