- A+
一、装饰器的作用
我个人的理解是:ts
中的装饰器类似于 Java
语言中的注解,对于用户来说都是为类和属性等代码元素
添加额外的功能,而不改变代码元素
原有的结构。例如在 Java
中我们用的比较多的 Spring
框架中的注解 @Component
可以将一个类放置到 IoC
容器中进行托管,使用 @Autowired
来取出该对象进行使用等等。
二、类装饰器
1. 普通装饰器
先写一个最简单的类装饰器,它看起来就是一个方法:
function logClass(params:any) { console.log(params); }
使用方式如下,非常类似Java
的注解:
@装饰器名 class 被装饰的类 { ... }
然后我们编写一个类来进行装饰:
@logClass // <-- 类装饰器的使用方式 class HttpClient { constructor() { } getData() { } }
查看运行的结果:
可以看出,logClass
参数 params
中得到的是修饰的类(在console
中打印出类型是方法的原因是ts
中的类在编译成js
代码后,会变成一个函数)
为类扩展属性和方法
扩展属性
这里为被修饰的类添加一个 info
属性:
function logClass(params:any) { // 在原型链上添加附加属性 params.prototype.info = '附加信息' }
然后我们将这个属性打印出来:
let httpClient:any = new HttpClient(); console.log(httpClient.info);
运行结果:
tips:注意这里的创建的对象的类型需要指定为
any
才可以使用info
属性
扩展方法
同理,我们也可以为被修饰的类扩展方法:
function logClass(params:any) { params.prototype.run = function() { console.log("我是扩展的run方法"); } }
方法调用:
let httpClient:any = new HttpClient(); httpClient.run();
运行结果:
使用装饰器修改属性和重写方法
语法:
function 装饰器名(target:any) { return class extends target { 原有的某属性:any = "我是修改后的数据"; 原有的某方法() { //...重写的内容 } } }
示例:
function logClass(target:any) { return class extends target { apiUrl:any = "我是修改后的数据"; getData() { this.apiUrl += "-----"; console.log(this.apiUrl); } } }
被修饰的类:
@logClass class HttpClient { public apiUrl: string | undefined; constructor() { } getData() { } }
运行代码:
let httpClient:HttpClient = new HttpClient(); httpClient.getData();
运行结果:
可以看到,使用装饰器,我们不需要编写子类即可修改类的属性和重写类的方法了!
2. 装饰器工厂
当我们使用普通装饰器时可以发现,我们是无法传递参数的,而现在要学的装饰器工厂则可以传递参数。
语法格式:
function 装饰器名(参数列表) { return function(target: any) { ... } }
现在参数列表
中的是我们输入的参数,而 target
是被装饰的类。
示例:
function logClass(params:string, p2?:string) { return function(target: any) { console.log("params",params); console.log("p2",p2); console.log("target",target); } }
被装饰的类:
@logClass("hello world", "第二个参数") class HttpClient { constructor() { } getData() { } }
运行结果:
三、属性装饰器
定义方式:
function 属性装饰器名() { return function(target: any, attr: any) { ... } }
这里返回的 function
有两个参数:
- 对于静态成员来说是类的
构造函数
,对于实例成员来说是类的原型对象
- 成员的名字(
string
类型)
例如我们可以使用下面的方式使用属性装饰器来修改属性:
属性装饰器:
function logProperty(params: any) { return function(target: any, attr: any) { target[attr] = params; } }
被装饰的类:
// @logClass class HttpClient { @logProperty("hello world") public apiUrl: string | undefined; constructor() { } getData() { } }
打印属性:
let httpClient:HttpClient = new HttpClient(); console.log(httpClient.apiUrl);
运行结果:
可以看到属性的值被修改为了我们传入到注解中的值了。
四、方法装饰器
定义方式:
function 装饰器名(参数列表) { return function(target:any, methodName:any, desc:any) { ... } }
这里返回的 function
有三个参数:
target
是修饰的类的对象原型methodName
是方法名desc
方法的描述信息对象
我们可以将它们打印出来:
function Get(url:string) { return function(target:any, methodName:any, desc:any) { console.log(target); console.log(methodName); console.log(desc); } }
class HttpClient { constructor() { } @Get("http://www.itying.com") getData() { } }
运行结果:
使用方法装饰器对方法进行扩展
被修饰类:
class HttpClient { constructor() { } getData(...args:any[]) { console.log(args); } }
此时我们希望扩展 getData()
方法的功能:在执行前后进行日志打印,并将传入的参数先转换为 string
类型。
具体的装饰器如下:
function logMethod() { return function(target:any, methodName:any, desc:any) { // 先保存原来的函数 let originMethod:Function = desc.value; // 修改函数的实现 desc.value = function(...args:any[]) { // 数据预处理(转为string) args = args.map(value=>String(value)); // 运行前日志 console.log(`====函数${methodName}运行前====`); // 调用原函数 originMethod.apply(this, args); // 运行后日志 console.log(`====函数${methodName}运行后====`); } } }
创建对象并调用方法:
let httpClient:HttpClient = new HttpClient(); httpClient.getData(1,2,3);
运行结果:
五、方法参数装饰器
定义方法:
function logParam(){ return function(target:any, methodName:any,paramIndex:any) { ... } }
定义方法和方法装饰器基本一致,区别在于三个参数不同:
target
是修饰的类的对象原型methodName
是方法名paramIndex
是参数的索引
示例:
function logParam(){ return function(target:any, methodName:any,paramIndex:any) { console.log(target); console.log(methodName); console.log(paramIndex); } }
被修饰的类
class HttpClient { // @logProperty("hello world") public apiUrl: string | undefined; constructor() { } getData(@logParam() str:string) { console.log("getData函数被调用了"); } }
运行结果:
六、装饰器的执行顺序
属性装饰器 => 方法装饰器 => 方法参数装饰器 => 类装饰器