什么是装饰器
官方解释: 装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
个人理解: 装饰,自然是为了起辅助作用,例如你做了一个程序,但是这个程序不满足你的要求,你想修改或者增添一些功能,这个时候我们就可以用到装饰器。
环境搭建
- 首先要跑ts代码的话,得先安装typescript
npm install typescript -g
在根目录下输入tsc命令,如果没报错代表安装成功。
- 创建tsconfig文件,根目录下就会生成tsconfig.json文件
tsc --init
3. 接着在tsconfig.json文件搜索 decorator,如下图,解开文件中的注释,接下来你就可以愉快的开发了。
- 执行代码
- 监控对应的文件夹下的文件,执行该命令后会生成对应的js文件
tsc -w
- 执行对应的js文件,index.js是对应的文件名,注意这里要看终端的目录是否正确
node index.js
装饰器类型
装饰器的本质就是一个函数,根据装饰器修饰的值的不同,将装饰器进行了分类。修饰什么类型的值就就是什么装饰器。
类装饰器
类装饰器比较简单,我们来简单看个例子。
const MessageDecorator: ClassDecorator = (target: Function) => {
target.prototype.message = (context:string) => {
console.log(context);
}
}
@MessageDecorator
class LoginController {
public login () {
console.log('登入业务处理');
console.log('登入成功消息');
(this as any).message('恭喜登入成功');
}
}
new LoginController().login();
类装饰器只有一个参数target,他是一个构造函数,该装饰器在原型上加了一个message方法,所以类中可以直接调用。
方法装饰器
接下来,我们依旧看一段代码来讲解方法装饰器,这是多种代码的组合,可能会报错,主要是讲解其中的知识点。
const showDecorator: MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
target.name = 'zs';
descriptor.value = () => {
console.log('后盾人');
}
descriptor.writable = false;
}
class User {
// 方法装饰器:如果是修饰普通方法,则args[0]是原型对象,如果修饰的是静态方法,则args[0]是构造函数
@showDecorator
public static show() {
console.log('houdunren.com');
};
}
// console.log(new User().name);
// new User().show();
User.show = () => {
console.log("12");
}
方法装饰器有三个参数,
第一个参数target: 如果是修饰普通方法,则target代表的是原型对象,如果修饰的是静态方法(static),target则是一个构造函数。
第二个参数propertyKey: 是函数的名字,例如这里则是 ‘show’
第三个参数descriptor: 主要是函数的一些配置属性,这里介绍2个比较常用的
descriptor.value // 可以获取到该函数
descriptor.writable // 控制该函数是否可以修改
属性装饰器
const VisitDecorator:PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
let value: string | undefined;
Object.defineProperty(target,propertyKey,{
get: () => {
return value?.toLowerCase();
},
set: (v:string) => {
value = v;
}
})
}
class Visit {
@VisitDecorator
title: string | undefined
}
const obj = new Visit();
obj.title = 'JEKS;FKSLFSFS';
console.log(obj.title);
第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
第二个参数: 成员的名字。
该代码也比较简单,在装饰器中对title属性进行拦截,将大写字母转化成小写。
参数装饰器
const RequiredDecorator: ParameterDecorator = function(target:any,propertyKey:string | symbol,parameterIndex:number) {
let requiredParams: number[] = [];
requiredParams.push(parameterIndex);
}
class ValidateUser {
find(name:string,@RequiredDecorator id:number) {
console.log(id);
}
}
参数装饰器的的前两个参数和方法装饰器相同,第三个参数代表的是被装饰器修饰的参数,在函数中的位置索引,例如此处parameterIndex则为1,find函数中装饰器修饰的id的位置索引为1
装饰器番外
装饰器工厂
装饰器工厂本质就是一个高阶函数,让装饰器可以传递参数,接下来我用一个延时函数来了解下
const sleepDecoratorFactory = (waitTime:number) => (target:any,propertyKey:string | symbol,descriptor:PropertyDescriptor) => {
target.name = 'zs';
const value = descriptor.value;
descriptor.value = () => {
setTimeout(() => {
value();
},waitTime)
}
}
class Music {
@sleepDecoratorFactory(2000)
public play() {
console.log("start play");
}
}
new Music().play();
console.log((new Music() as any).name);
元数据
对于数据的描述,接下来我们看看怎么使用
- 安装依赖
npm i reflect-metadata --save
import 'reflect-metadata';
const RequiredDecorator: ParameterDecorator = function(target:any,propertyKey:string | symbol,parameterIndex:number) {
let requiredParams: number[] = [];
requiredParams.push(parameterIndex);
Reflect.defineMetadata('required',requiredParams,target,propertyKey)
}
const validateDecorator: MethodDecorator = (target:any,propertyKey: string | symbol,descriptor:PropertyDescriptor) => {
console.log(Reflect.getMetadata('required',target,propertyKey));
const method = descriptor.value;
descriptor.value = function() {
const requiredParams:number[] = Reflect.getMetadata('required',target,propertyKey) || [];
requiredParams.forEach(index => {
if (index > arguments.length || arguments[index] === undefined) {
throw new Error('请传递必要参数')
} else {
method.apply(this,arguments)
}
})
}
}
class ValidateUser {
@validateDecorator
find(name:string,@RequiredDecorator id:number) {
console.log(id);
}
}
new ValidateUser().find('zs',1)
Reflect.defineMetadata(‘required’,requiredParams,target,propertyKey);
第一个参数,相当于获取数据的唯一key,
第二个参数,对于数据的描述数据,
第三个参数,对象
第四个参数,对象中的具体属性
Reflect.getMetadata(‘required’,target,propertyKey),通过可以可以获取到requiredParams,之前defineMetadata中存储的数据