TypeScript学习笔记(四)装饰器

  • A+
所属分类:Web前端
摘要

我个人的理解是:ts中的装饰器类似于 Java 语言中的注解,对于用户来说都是为类和属性等代码元素添加额外的功能,而不改变代码元素原有的结构。例如在 Java 中我们用的比较多的 Spring 框架中的注解 @Component 可以将一个类放置到 IoC 容器中进行托管,使用 @Autowired 来取出该对象进行使用等等。

一、装饰器的作用

我个人的理解是:ts中的装饰器类似于 Java 语言中的注解,对于用户来说都是为类和属性等代码元素添加额外的功能,而不改变代码元素原有的结构。例如在 Java 中我们用的比较多的 Spring 框架中的注解 @Component 可以将一个类放置到 IoC 容器中进行托管,使用 @Autowired 来取出该对象进行使用等等。

二、类装饰器

1. 普通装饰器

先写一个最简单的类装饰器,它看起来就是一个方法:

function logClass(params:any) {   console.log(params); } 

使用方式如下,非常类似Java的注解:

@装饰器名 class 被装饰的类 {   ... } 

然后我们编写一个类来进行装饰

@logClass // <-- 类装饰器的使用方式 class HttpClient {   constructor() {   }   getData() {   } } 

查看运行的结果:

TypeScript学习笔记(四)装饰器

可以看出,logClass 参数 params 中得到的是修饰的类(在console中打印出类型是方法的原因是ts中的在编译成js代码后,会变成一个函数

为类扩展属性和方法

扩展属性

这里为被修饰的类添加一个 info 属性:

function logClass(params:any) { 	// 在原型链上添加附加属性   params.prototype.info = '附加信息' } 

然后我们将这个属性打印出来:

let httpClient:any = new HttpClient(); console.log(httpClient.info); 

运行结果:

TypeScript学习笔记(四)装饰器

tips:注意这里的创建的对象的类型需要指定为 any 才可以使用 info 属性

扩展方法

同理,我们也可以为被修饰的类扩展方法:

function logClass(params:any) {   params.prototype.run = function() {     console.log("我是扩展的run方法");   } } 

方法调用:

let httpClient:any = new HttpClient(); httpClient.run(); 

运行结果:

TypeScript学习笔记(四)装饰器

使用装饰器修改属性和重写方法

语法:

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(); 

运行结果:

TypeScript学习笔记(四)装饰器

可以看到,使用装饰器,我们不需要编写子类即可修改类的属性重写类的方法了!

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() {   } } 

运行结果:

TypeScript学习笔记(四)装饰器

三、属性装饰器

定义方式:

function 属性装饰器名() {   return function(target: any, attr: any) {     ...   } } 

这里返回的 function 有两个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  2. 成员的名字(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); 

运行结果:

TypeScript学习笔记(四)装饰器

可以看到属性的值被修改为了我们传入到注解中的值了。

四、方法装饰器

定义方式:

function 装饰器名(参数列表) {   return function(target:any, methodName:any, desc:any) {     ...   }  } 

这里返回的 function 有三个参数:

  1. target 是修饰的类的对象原型
  2. methodName 是方法名
  3. 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() {   } } 

运行结果:

TypeScript学习笔记(四)装饰器

使用方法装饰器对方法进行扩展

被修饰类:

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); 

运行结果:

TypeScript学习笔记(四)装饰器

五、方法参数装饰器

定义方法:

function logParam(){   return function(target:any, methodName:any,paramIndex:any) {     ...   } } 

定义方法和方法装饰器基本一致,区别在于三个参数不同:

  1. target 是修饰的类的对象原型
  2. methodName 是方法名
  3. 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函数被调用了");   } } 

运行结果:

TypeScript学习笔记(四)装饰器

六、装饰器的执行顺序

属性装饰器 => 方法装饰器 => 方法参数装饰器 => 类装饰器