TypeScript装饰器、装饰器的执行顺序

本文详细介绍了TypeScript的装饰器概念,包括类装饰器、属性装饰器、方法装饰器、参数装饰器,以及它们的执行顺序。装饰器允许在运行时修改或扩展类的行为。文章通过实例展示了如何使用装饰器来改变类、属性、方法和参数的功能,并解释了装饰器的参数和执行流程。

装饰器

特殊的类型声明,它能够附加到类声明,方法,属性或参数上,可以修改类的行为。

通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。

常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器。

装饰器的写法:普通装饰器(无参)、装饰工厂(可传参)

装饰器是过去几年中js 最大的成就之一,已是ES7的标准特征之一。

普通类装饰器

类装饰器是在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或者替换类定义。

//定义装饰器
function logClass(params:any){
    console.log(params:any);
  //这里的params 就是当前类
}

@logClass    //调用装饰器
class HttpClient{
    constructor(){

    }
    getData(){

    }
}

  打印结果:                     

那么已知是当前类,那么就可以操作该类,可以扩展,修改这个类。

//定义装饰器
function logClass(params:any){
    console.log(params:any);
  //这里的params 就是当前类
    params.prototype.apiUrl = 'xxx';
    params.prototype.run = function{
        console.log("--------run---------");
    }
}

@logClass    //调用装饰器
class HttpClient{
    constructor(){

    }
    getData(){

    }
}
var http:any = new HttpClient();
console.log(http.apiUrl);
http.run();

 带参数的类装饰器(装饰器工厂)

//定义装饰器工厂
function logClass(params:string){
    return function(target:any){
        console.log(target);
        console.log(params);
    }
    target.prototype.apiUrl = params; //属性装饰器
}

@logClass("www.baidu.com")    //调用装饰器
class HttpClient{
    constructor(){

    }
    getData(){

    }
}

var  http:any = new HttpClient();
console.log(http.apiUrl);

   通过打印可以看到,将字符串hello 赋值给了params,将对象赋值给了target.

类装饰器重载构造函数

装饰器表达式会在运行时当做函数被调用,类的构造函数作为其唯一的参数。

如果类装饰器返回一个值,他会使用提供的构造函数来替换类的声明。

function logClass(target:any){
    console.log(target);
    //重载构造函数    
   return class extends target {
        apiUrl:any = "我是修改后的数据";
    }
    
    getData(){
        this.apiUrl  = this.apiUrl+'----';
        console.log(this.apiUrl);
    }    

}

@logClass
class HttpClient{
   public apiUrl:string | undefined;
    constructor(){
        this.apiUrl = "我是构造函数里面的apiUrl";
    }
    getData(){
      console.log(this.apiUrl);
    }
}

var  http = new HttpClient();
http.getData();

属性装饰器

属性装饰表达式会在运行时当做函数被调用,传入下列两个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
//类装饰器
function logClass(params:string){
     
   return function(target:any){
        console.log(target);
        console.log(target);
    }

}

//属性装饰器
function logProperty(params:any){
    
        return function(target:any,attr:any){
            console.log(target);
            console.log(attr);
            target[attr] = params;
        } 
    }

@logClass('xxx')
class HttpClient{
   @logProperty("http://www.baidu.com")
   public apiUrl:string | undefined;
    constructor(){
      
    }
    getData(){
      console.log(this.apiUrl);
    }
}

属性装饰器 中打印的 target就是当前对象, 而attr 就是属性 apiUrl,所以可以根据 

   target[attr] = params;
来修改属性。

方法装饰器

方法装饰器它会被应用到方法的属性描述符上,可以来监视,修改或者替换方法定义。

方法装饰器会在运行时传入下列3个参数:

1、对静态成员来说是累的构造函数,对于实例成员是累的原型对象。

2、成员的名字。

3、成员的属性描述符。

 

//方法装饰器
function logmethod(params:any){
    return function(target:any,methodName:any,desc:any){
      console.log(target);
      console.log(methodName);
      console.log(desc);

      target.apiUrl = 'xxxx';
      target.run = function(){
            console.log(this.url);
        }
    }
}

class HttpClient{
    public apiUrl:string | undefined;
    constructor(){
      
    }

    @logmethod('www.baidu.com')
    getData(){

    }
}
var http:any = new HttpClient();
console.log(http:apiUrl);
http.run();

同理,target 还是这个原型对象,methodName就是getData 即方法名称, desc是个对象,对象中的value 就是方法名称。

 

演示:用方法装饰器修改方法,将方法装饰器里面传入的所有参数改为string 类型。

原理:修改desc.value

//方法装饰器
function logmethod(params:any){
    return function(target:any,methodName:any,desc:any){
      console.log(target);
      console.log(methodName);
      console.log(desc);
      // 1保存当前方法
        var oldMethod = desc.value;
        desc.value = function(...args:any[]){//用三点运算符来接收传入的数组参数
            args = args.map((value)=>{
                return String(value);
    
            })  
            console.log(args);    
        }
     
    }
}

class HttpClient{
    public apiUrl:string | undefined;
    constructor(){
      
    }

    @logmethod('www.baidu.com')
    getData(){
        console.log("我是getData里面的方法");
    }
}

var http = new HttpClient();
http.getData("123","xxx");

 

 

很明显getData方法被重写了。 

那么如果想修改原来的getData方法那应该这么写

//方法装饰器
function logmethod(params:any){
    return function(target:any,methodName:any,desc:any){
      console.log(target);
      console.log(methodName);
      console.log(desc);
      // 1保存当前方法
        var oldMethod = desc.value;
        desc.value = function(...args:any[]){//用三点运算符来接收传入的数组参数
            args = args.map((value)=>{
                return String(value);
    
            })  
            console.log(args);
             oldMethod.apply(this,args);   
        }
     
    }
}

class HttpClient{
    public apiUrl:string | undefined;
    constructor(){
      
    }

    @logmethod('www.baidu.com')
    getData(){
        console.log("我是getData里面的方法");
    }
}

var http = new HttpClient();
http.getData("123","xxx");


这里的  oldMethod.apply(this,args);   中的this表示

 desc.value = function(...args:any[]){ } ,因为是在当前方法中操作的,所以this 指代当前方法。

而args代表参数,它的整体含义就是,在新的方法中传入参数然后调用原来的方法。
       

 

可以很明显的看到,原来的getData方法被成功执行。

如果此时将...args:any[] 参数数组传入原来的getData方法中即代码如下

//方法装饰器
function logmethod(params:any){
    return function(target:any,methodName:any,desc:any){
      console.log(target);
      console.log(methodName);
      console.log(desc);
      // 1保存当前方法
        var oldMethod = desc.value;
        desc.value = function(...args:any[]){//用三点运算符来接收传入的数组参数
            args = args.map((value)=>{
                return String(value);
    
            })  
            console.log(args);
             oldMethod.apply(this,args);   
        }
     
    }
}

class HttpClient{
    public apiUrl:string | undefined;
    constructor(){
      
    }

    @logmethod('www.baidu.com')
    getData(...args:any[]){
      console.log(args:any);
      console.log("我是getData里面的方法");
    }
}

var http = new HttpClient();
http.getData("123","xxx");


 

打印的结果如下

可以看到原来的方法中的参数全部变成了string 类型,

这就是方法装饰器,它可以修改方法,也可以修改传入参数类型。

方法参数装饰器

参数装饰器表达式会在运行时当做函数被调用,可以使用参数装饰器为类的原型增加一些数据,传入下列三个参数:

1、对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。

2、参数的名字。

3、参数在函数参数列表中的索引。

//方法参数装饰器
function logparams(params:any){
  
      return function(target:any,paramsName:any,paamsIndex:any){
          console.log(params);
          console.log(target);
          console.log(paramsName);
          console.log(paamsIndex);

       }
}

class HttpClient{
    public apiUrl:string | undefined;
    constructor(){
      
    }

    getData(@logparams('xxxxx'uuid:any)){
      console.log("我是getData里面的方法");
    }
}

var http = new HttpClient();
http.getData(123456);


uuid成功被打印。

装饰器执行顺序

//定义装饰器工厂

function logClass1(params:string){
    return function(target:any){
        console.log('类装饰器1');
    }
}

function logClass2(params:string){
    return function(target:any){
        console.log('类装饰器2');
    }
}

//属性装饰器
function logProperty(params?:any){
 
        return function(target:any,attr:any){
            console.log('属性装饰器');
        } 
    }


//方法装饰器
function logmethod(params:any){
    return function(target:any,methodName:any,desc:any){
      console.log('方法装饰器');
    }
}

//方法参数装饰器
function logparams1(params:any){
  
      return function(target:any,paramsName:any,paamsIndex:any){
          console.log('方法参数装饰器1');
       }
}

function logparams2(params:any){
  
      return function(target:any,paramsName:any,paamsIndex:any){
          console.log('方法参数装饰器2');
       }
}


@logClass1("www.baidu.com")
@logClass2("www.IMOOC.com")
class HttpClient{
   @logProperty()//可以不传参
   public apiUrl:string | undefined;
    constructor(){
      
    }
    @logmethod('www.baidu.com')
    getData(){
      console.log(this.apiUrl);
    }
   setData(@logparams1('xxxxx'uuid:any),@logparams2('xxxxx'uuid:any)){
      console.log(this.apiUrl);
    }

}



var  http:any = new HttpClient();

装饰器执行顺序

属性》方法》方法参数》类

如果有多个同样的装饰器,它会先执行后面的装饰器。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值