10. TS 装饰器及执行顺序

1,365 阅读3分钟

一、ts 装饰器概念

装饰器:装饰器是一种特殊类型的声明,它能够被添加到类声明,方法,属性或参数上,可以修改类的形为

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

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

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

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

所有装饰器后面都不可加分号 ;

二、类装饰器

类装饰器:类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视、修改、替换 类的定义。可传入一个参数

1. 类装饰器:普通装饰器(无法传参)

function logClass(params:any) {
    // 这里的 parmas 默认表示被监视的类

    // 给类 添加属性
    params.prototype.name = 'xxl'

    // 给类 添加方法
    params.prototype.getUrl = function() {
        return this.name + '123'
    }
}

// 装饰饰后面不能加 分号 ;
@logClass
class Animal{
    
    constructor() {
    }
}

// 这里要加个:any,因为我们没有传入 类初始不用传参,是利用装饰器使得类加了属性
let dog:any = new Animal()

console.log(dog.name)        // xxl
console.log(dog.getUrl())   // xxl123

2. 类装饰器:装饰器工厂(可传参)

function logClass(params:string) {
    // 这里的 params 就是装饰器传过来的参数

    // 这里的返回的函数就是装饰器监视的类
    return function(target:any) {
        
        // 给类 添加属性
        target.prototype.url = params;

        // 给类 添加方法
        target.prototype.getUrl = function() {
            return this.url + '123'
        }
    }
}

@logClass('www.baidu.com')
class Animal{
    constructor() {

    }
}

let d:any = new Animal()
console.log(d.url)   // www.baidu.com
console.log(d.getUrl())   // www.baidu.com123

3. 类装饰器:重载构造函数

类装饰器:重载构造函数, 通过使用 return class extends target{ } 来重载

以下是一个重载构造函数的例子:

类装饰器表达式会在运行时当作函数被调用, 类的构造函数会作为其唯一的参数。 如果类装饰器返回一个值, 它会使用提供的构造函数【class extends target{ } 】来替换类的声明

function logClass(target:any) {

    return class extends target{

        apiUrl = '新的地址'
        
        getUrl() {
            return this.apiUrl + 'new'
        }
    }
}

@logClass

class Animal {
    apiUrl:string | undefined;

    constructor() {
        this.apiUrl = '旧的地址'
    }

    getUrl() {
        return this.apiUrl + 'old'
    }

}

let d = new Animal()
console.log(d.getUrl())

三、属性装饰器

属性装饰器表达式会在运行时当作函数被调用,传入下列 2 个参数:
1、对于静态成员来说是类的构造函数,对于实例成员来说是 类的原型对象 2、属性成员的名字, 例如:name, age 等等。

function logProperty(params:any) {

    //  target 是类的原型对象
    return function(target:any, attr:any) {
        target[attr] = params;

        console.log( target[attr], `属性为:${attr}`)      
    }

 }

 class Animal{
    @logProperty(20)
    total:number | undefined;

    @logProperty('wwww.baidu.com')
    apiUrl:string | undefined;


    @logProperty('gg')
    name:string | undefined;

    constructor() {
    }

    getUrl() {
        console.log('apiUrl的值为' + this.apiUrl) 
    }
 }


 let d = new Animal()
 console.log(d.total, 'd.total')  // 20 "属性为:total"
 console.log(d.apiUrl, 'd.apiUrl') // wwww.baidu.com 属性为:apiUrl
 console.log(d.name, 'd.name')    // gg 属性为:name
 d.getUrl()                      // apiUrl的值为wwww.baidu.com

四、方法装饰器

方法装饰器: 它会被应用到方法的 属性描述符上, 可以用来监视、修改或者替换方法定义, 同时可以衍生原来的方法的作用

方法装饰器在运行时传入下列 3 个参数
1. target: 对于静态成员来说是类的构造函数对于实现成员来说是类的原型对象
2. methodName: 方法成员的名字, 如 getUrl
3. desc: 方法成员的属性描述符

1. 方法装饰器一:修改类里的属性和方法

function get(params:any) {
    return function(target:any, methodName:any, desc:any) {
        console.log(target, 'target')   // 
        console.log(methodName, 'methodName')
        console.log(desc, 'desc')

        // 建议先在类中定义一下,再进行修改,不然会报错  Property 'name' does not exist on type 'HttpClient'.
        target.name = 'gg';
        target.run = function() {
            return '在跑步'
        }
    }
}

class HttpClient{

    name:string | undefined;
    apiUrl:string | undefined;

    constructor() {}

    @get('www.baidu.com')
    getUrl() {
        return this.apiUrl;
    }
    
    run() {
        return '*****'
    }
}

let http = new HttpClient()
console.log(http.name)    // gg
console.log(http.run())   // 在跑步

2. 方法装饰器二:装饰方法

装饰方法就是把之前的方法先保留目前的操作,再在这基础上增加一些操作。这个功能我们可以通过用 对象冒充 apply()call() 来实现

function logMethods(params:any) {
    return function(target:any, methodName:any, desc:any) {
        // 先保存之前方法
        let oMethod = desc.value;
        // 再修改方法,例如:我们目前想把所有的参数转化为 string 类型
        desc.value = function(...args:any[]) {
            args = args.map((value) => {
                return String(value)
            })
            // 再利用对象冒充的形式定义到我们之前的方法
            oMethod.apply(this, args)
            console.log(args, 2)   // ["12", "abc"] 
        }
    }
}

class HttpClient {
    @logMethods('2222')
    getUrl(...args:any[]) {
        console.log(args, '1')  // ["12", "abc"] 
        console.log('这是当前的方法操作相关') 
    }
}

let http = new HttpClient()
console.log(http.getUrl(12,  'abc'), '----')

五、方法参数装饰器

方法参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列 3 个参数
1. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
2. 方法的名字, 例如:getUrl
3. 参数在函数参数列表中的索引

function logParams(parmas:any) {
   return function (target:any, methodName:any, paramsIndex:any) {
       console.log('------')
       console.log(parmas, 'parmas')   // 这是装饰饰传的参数 parmas
       console.log(target, 'target')
       console.log(methodName, 'methodName')  // getUrl methodName
       console.log(paramsIndex, 'paramsIndex')  // 0 "paramsIndex"

       if(paramsIndex === 1) {
           
       }
   }
}

class HttpClinet{
   getUrl(@logParams('这是装饰饰传的参数1') a:any,@logParams('这是装饰饰传的参数2') b:any) {
       console.log(a, b)
   }
}

let http = new HttpClinet();
http.getUrl('这是一个阳光', '好天气')

六、装饰器执行顺序

执行顺序 属性>方法>方法参数>类
注:如果有多个同样的装饰器,它会先执行后面的装饰器。

function logClassA(params:any) {
   return function(target:any) {
       console.log('类 装饰器----111')

   }
}

function logClassB(params:any) {
   return function(target:any) {
       console.log('类 装饰器----222')

   }
}

function logAttrA(params:any) {
   return function(target:any, attr:any) {
       console.log('属性 装饰器---' + `${params}`)

   }
}
function logAttrB(params:any) {
   return function(target:any, attr:any) {
       console.log('属性 装饰器---' + `${params}`)

   }
}
function logMethodsA(params:any) {
   return function(target:any, methodName:any,desc:any ) {
       console.log('方法 装饰器----' + `${params}`)

   }
}
function logMethodsB(params:any) {
   return function(target:any, methodName:any,desc:any ) {
       console.log('方法 装饰器----' + `${params}`)

   }
}
function logMethodsParamsA(params:any) {
   return function(target:any, methodName:any,paramsIndex:any ) {
       console.log('方法参数 装饰器----' + `${params}`)

   }
}
function logMethodsParamsB(params:any) {
   return function(target:any, methodName:any,paramsIndex:any ) {
       console.log('方法参数 装饰器----' + `${params}`)

   }
}

@logClassA('1111')
@logClassB('2222')
class HttpClient{

   @logAttrA('1111')
   @logAttrB('2222')
   urlA:string | undefined
  
   @logMethodsA('111')
   @logMethodsB('222')
   getUrlA(@logMethodsParamsA('111') name:any, @logMethodsParamsB('222') age:any) {

   }
   
}

执行结果如下图:

image.png