装饰器Decorators
装饰器基本了解
- 装饰器的目的就是为了给代码添加新的功能,随着程序的功能越来越多,需要给某一个小块添加上功能,这时可以使用装饰器,对类进行方法的新增操作,装饰器与ES6 中的面向对象的继承式有一定的区别的,但是本质上,他们都是在操作原型对象,通过给原型对象 prototype 添加一些方法和属性,来扩展类的功能
- 装饰器就是装饰类的,类自身有很多的功能,我们也可以通过装饰器,在额外的给类添加新的功能
- 装饰器不仅仅可以操作类,也可以对类自身的方法进行重写
环境的搭建
- 需要初始化一个 TS 的配置文件,并且侦听 TS 文件的编译运行,通过 tsc --init 去初始化一个装饰器
- 运行 TS, tsc -w 执行编译
- 在配置文件中找到以下两项,并且将他进行开启
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
装饰器类别
名称 | 装饰器作用 |
---|---|
ClassDecorator | 类(class)的装饰器 |
MethodDecorator | 方法装饰器 |
PropertyDecorator | 属性装饰器 |
ParameterDecorator | 参数装饰器 |
类装饰器
类的装饰器
装饰器的定义,装饰器的本身,他是一个函数,会在运行的时候被调用,被装饰的类,会作为参数传递给装饰器函数,当作形参。
类装饰器接收一个构造函数作为参数,参数的类型是一个函数
const moveDecortor:ClassDecorator = (target:Function):any => {
console.log(target);
}
@moveDecortor
class User {
}
装饰器的使用后方式,定义完成装饰器,通过 @ + 装饰器名称,将他书写在要装饰的类的前面,这样的含义就是,当前装饰器下面的类,会一参数的形式,传递给装饰器函数
{
const moveDecortor:ClassDecorator = (target:Function):any => {
target.prototype.forName = ():number => {
return 1
}
}
@moveDecortor
class User{} 1. 可以在类中定义同名方法,消除报错
const u = new User()
console.log((<any>u).forName());
}
装饰器的叠加
装饰器不仅仅只能使用一个,可以定义多个装饰器,作用于一个类身上,通过叠加装饰器的方式,给类追加多个方法和属性
{
const moveDecortor:ClassDecorator = (target:Function):any => {
target.prototype.forName = ():number => {
return 1
}
}
const musicDecortor: ClassDecorator = (target:Function):any => {
target.prototype.block = ():void => {
console.log('播放音乐');
}
}
@moveDecortor
@musicDecortor
class User{
public forName() {}
public block() {}
}
const u = new User()
console.log((u.forName())
u.block()
}
装饰器工厂
装饰器工厂的作用就是,根据使用装饰器的类,类给装饰器工程传递数据,根据数据返回不同的装饰器,这个函数被称之为 装饰器工厂
{
const musicDecoratorFactory = (str: string):ClassDecorator => {
switch (str) {
case 'Token':
return (target: Function):any => {
target.prototype.play = function():void {
console.log('播放音乐' + str);
}
};
default:
return (target: Function):any => {
target.prototype.play = function():void {
console.log('播放音乐' + str);
}
};
}
}
@musicDecoratorFactory('Token')
class Music {
public play() {}
}
new Music().play()
}
方法装饰器
方法装饰器与类装饰器的使用方式相同,但是装饰器作用的位置式不同的,方法装饰器需要用在类方法的前面,将方法作为参数,传递给装饰器
const showDecorator:MethodDecorator = ( ...args:any[] ):any => {
console.log(args)
}
class User {
@showDecorator
public show () {}
}
args的打印结果
[
1. 如果是静态方法,这里就是构造函数,如果是普通方法就是原型对象
{ show: [Function (anonymous)] },
'show', 2. 函数名
{ 3. 该函数可以被执行的操作,是否可读写,可复制
value: [Function (anonymous)],
writable: true,
enumerable: true,
configurable: true
}
]
不使用数组接收参数,这样的好处就是,在操作接收的参数的时候,不需要通过数组下标的方式去找到,要操作的对象,直接调用,代码可阅读性的提高
const showDecorator:MethodDecorator = ( target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor ):any => {
console.log(args)
}
class User {
@showDecorator
public show () {}
}
延时器在装饰器里面使用 & 装饰器工厂
{
const setTimeoutDecorator:MethodDecorator = (...args:any[]):any => {
const [,,descriptor] = args;
const msg = descriptor.value;
descriptor.value = () =>{
setTimeout(() => {
msg()
},3000)
}
}
class SetTime {
@setTimeoutDecorator
public times() {
console.log('延时加载');
}
}
new SetTime().times();
}
这里需要注意的事项就是,在装饰器里,使用的技巧就是先将原有的值进行存储一下,在去使用,确保它使用的是类中的方法中的值
全局错误粗合理
- 通过使用装饰器可以对 方法抛出的错误,将错误提示通过装饰器进行重写,也就是字定义错误信息
{
const ErrorDecorator:MethodDecorator = (...args: any[]) => {
const [,,descriptor] = args;
const method = descriptor.value
descriptor.value = () =>{
try {
method()
} catch (error) {
console.log('这是新定义的错误')
}
}
}
class User {
@ErrorDecorator
public errorMsg () {
throw new Error('报错了')
}
}
new User().errorMsg()
}
属性装饰器与方法装饰器
属性装饰器的使用
{
const nameDecorator:PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
Object.defineProperty(target,propertyKey, {
set (v) {
console.log(v);
}
})
}
class User {
@nameDecorator
public str: string;
constructor(str: string) {
this.str = str
}
}
console.log(new User('小明'));
}
属性装饰器的参数
[ {}, 'str', undefined ]
1. str 属性名称
2. {} 原型链 || 构造函数
方法参数访问器
{
const nameDecorator:ParameterDecorator = (...args: any[]) => {
console.log(args);
}
class User {
public getName(num:number,@nameDecorator str: string) {
console.log(num,str);
}
}
new User().getName(1,'小明')
}
参数的介绍
target: Object, propertyKey: string | symbol, parameterIndex: number
[ { getName: [Function (anonymous)] }, 'getName', 0 ]
第一个代表的是当前方法
第二个代表的是方法名称
第三个代表的是,当前参数在整个方法参数中,所在的位置
元数据
首先来看普通的数据创建
let U = {
name: '普通数据'
}
元数据是普通数据的数据,我们可以给 name 属性额外的添加一些描述,这类数据被称为 元数据,在 TS 中默认是没有元数据的,需要通过 npm 下载相关包文件
npm init -y
npm i reflect-metadata --save
使用元数据
- 使用【Reflect.defineMetadata】创建一个元数据
- 参数一:元数据名称
- 参数二:元数据的值
- 参数三:要被设置元数据信息的对象
- 参数四:对象内部哪一个属性要设置元数据,属性名称
import 'reflect-metadata'
{
let u = {
name: '元数据'
}
Reflect.defineMetadata('md',{sex: '男'},u, 'name')
console.log(Reflect.getMetadata('md',u,'name'));
}
- 通过 【Reflect.getMetadata】获取元信息
- 元数据名称
- 哪一个对象,对象里面哪一个属性