装饰器(decorator)最早在 Python 中被引入,它的主要作用是给一个已有的方法或类扩展一些新的行为,而不是去直接修改它本身。在 ES2015 进入 Class 之后,当我们需要在多个不同的类之间共享或者扩展一些方法或行为的时候,代码会变得错综复杂,极其不优雅,这也就是装饰器被提出的一个很重要的原因.
所以在 JavaScript 中我们需要 Babel 插件 babel-plugin-transform-decorators-legacy 来支持 decorator,而在 Typescript 中我们需要在 tsconfig.json 里面开启支持选项 experimentalDecorators.

  1. // tsconfig.json
  2. "experimentalDecorators": true

装饰器概念

它是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。 通俗的讲装饰器就是一个函数/方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。


常见的装饰器有:

  • 类装饰器
  • 属性装饰器
  • 访问器装饰器
  • 方法装饰器
  • 参数装饰器

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

类装饰器

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


参数

  1. targetClass : new (…any: any) => any 构造函数

    不带参数的类装饰器

    ```typescript // 装饰器一 function FirstClassDecorator(targetClass: new (…any: any) => any) { let targetClassObj = new targetClass(‘wsy1’); targetClassObj.buy(); console.log(‘targetClass.name:’, targetClass.name); } // 装饰器二 function SecondClassDecorator(targetClass: any) { let targetClassObj = new targetClass(‘wsy2’); targetClassObj.buy(); console.log(‘targetClass.name:’, targetClass.name); }

@SecondClassDecorator @FirstClassDecorator class CustomerService { constructor(public name: string) { this.name = name; } buy() { console.log(this.name + ‘购买’); } placeOrder() { //下单 console.log(this.name + ‘下单购买’); } }

// wsy1购买 // targetClass.name: CustomerService // wsy2购买 // targetClass.name: CustomerService

  1. <a name="rrQtS"></a>
  2. ### 带参数的装饰器
  3. ```typescript
  4. function FirstClassDecorator(params: string) {
  5. console.log(params);
  6. return function (targetClass: new (...any: any) => any) {
  7. let targetClassObj = new targetClass('wsy1');
  8. targetClassObj.buy();
  9. console.log('targetClass.name:', targetClass.name);
  10. };
  11. }
  12. @FirstClassDecorator('params')
  13. class CustomerService {
  14. constructor(public name: string) {
  15. this.name = name;
  16. }
  17. buy() {
  18. console.log(this.name + '购买');
  19. }
  20. placeOrder() {
  21. //下单
  22. console.log(this.name + '下单购买');
  23. }
  24. }

重载构造函数

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


function logClassDecorator<T extends new (...args: any) => any>(
  classDecorator: T
) {
  return class LoggerDecorator extends classDecorator {
    constructor(...args: any) {
      console.log('这是做一次打印记录');
      super(...args);
    }
  };
}

@logClassDecorator
class Client {
  constructor(public name: string) {
    console.log('我实例化了');
    this.name = name;
  }
  getData() {
    console.log('这是我的name', this.name);
  }
}

const a = new Client('wsy');
a.getData();

属性装饰器

属性装饰器表达式会在运行时当作函数被调用


参数

  • target 装饰的实例。对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • propertyKey 装饰的属性名
function loginProperty(attrValue: any) {
  return function (targetclassPrototype: any, attrname: string | symbol) {
    console.log('targetclassPrototype:', targetclassPrototype);
    console.log('attrname:', attrname);
    targetclassPrototype[attrname] = attrValue;
    console.log('val', targetclassPrototype[attrname]);
  };
}

class Client {
  @loginProperty('test-wsy')
  public name!: string;
  constructor(name: string) {
    this.name = name;
  }
  getData() {
    console.log('这是我的name', this.name);
  }
}

const a = new Client('wsy');
a.getData();
console.log(a) // {name:wsy}
console.log(Object.getPrototypeOf(a));//{name:test-wsy}

访问器装饰器

访问器不过是类声明中属性的读取访问器和写入访问器。


参数

  • target 装饰的实例。对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • propertyKey 成员的名字
  • descriptor 成员的属性描述符 ```typescript function enumerable(value: boolean) { return function (
    target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      console.log('decorator - sets the enumeration part of the accessor');
      descriptor.enumerable = value;
    
    }; }

class Employee { private _salary: number; private _name: string;

@enumerable(false)
get salary() { return `Rs. ${this._salary}`; }

set salary(salary: any) { this._salary = +salary; }

@enumerable(true)
get name() {
    return `Sir/Madam, ${this._name}`;
}

set name(name: string) {
    this._name = name;
}

}

const emp = new Employee(); emp.salary = 1000; for (let prop in emp) { console.log(enumerable property = ${prop}); } // salary 属性不在清单上,因为我们将其设为假 // output: // decorator - sets the enumeration part of the accessor // decorator - sets the enumeration part of the accessor // enumerable property = _salary // enumerable property = name

<a name="Wbz66"></a>
## 方法装饰器
它会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义。方法装饰会在运行时传入下列个参数:

---

<a name="Qmlfl"></a>
### 参数

- **target**  装饰的实例。对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- **propertyKey **成员的名字
- **descriptor**  成员的属性描述符
```typescript
function MethodDecorator(params: any) {
  return function (
    target: any,
    methodName: string,
    descriptor: PropertyDescriptor
  ) {
    console.log(target);
    console.log(methodName);
    console.log(descriptor); //修改前保存原始传入的方法
    let originalMethod = descriptor.value; //重写传入的方法
    descriptor.value = function (...args: any) {
      //执行原来的方法
      console.log('前置拦截');
      originalMethod.call(this, ...args);
      console.log('后置拦截');
    };
  };
}

class Test {
  constructor(public name: string, public age: number) {
    this.name = name;
    this.age = age;
  }
  @MethodDecorator('test')
  youName(): string {
    console.log('class 方法执行');
    return this.name;
  }
}

const a = new Test('a', 1);
a.youName();

参数装饰器

运行时会被当做函数被调用,可以使用参数装饰器为类的原型增加一些元素数据


参数

  • target —— 装饰的实例,于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • propertyKey —— 方法名
  • index —— 参数数组中的位置 ```typescript function logParams(param: any) { return function (target: any, methodName: string, paramIndex: number) { console.log(target) // httpClient实例 console.log(methodName) // getApi console.log(paramIndex) // 0 } }

class HttpClient { constructor() { } getApi(@logParams(‘id’) id: number) { console.log(id) } }

const http = new HttpClient()

http.getApi(123456)

<a name="rU2me"></a>
## 装饰器工厂
由于每种装饰器都有它自身的调用签名,我们可以使用装饰器工厂来泛化装饰器调用。

---

```typescript
import { logClass } from './class-decorator';
import { logMethod } from './method-decorator';
import { logProperty } from './property-decorator';
import { logParameter } from './parameter-decorator';

// 装饰器工厂,根据传入的参数调用相应的装饰器
export function log(...args) {
    switch (args.length) {
        case 3: // 可能是方法装饰器或参数装饰器
            // 如果第三个参数是数字,那么它是索引,所以这是参数装饰器
            if typeof args[2] === "number") {
                return logParameter.apply(this, args);
            }
            return logMethod.apply(this, args);
        case 2: // 属性装饰器 
            return logProperty.apply(this, args);
        case 1: // 类装饰器
            return logClass.apply(this, args);
        default: // 参数数目不合法
            throw new Error('Not a valid decorator');
    }
}

@log
class Employee {
    @log
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    @log
    greet(@log message: string): string {
        return `${this.name} says: ${message}`;
    }
}

装饰器的执行顺序


function firstMethodDecorator(targetClassPrototype: any, methodname: string) {
  console.log('=============执行第一个方法装饰器==============');
  console.log('类名:', targetClassPrototype); //  类原型对象变量   URLInfo { show: [Function] }
  console.log('方法名:', methodname); //key
}

function secondMethodDecorator(params: string) {
  return function (targetClassPrototype: any, methodname: string) {
    console.log('=============执行第二个方法装饰器==============');
    console.log('类名:', targetClassPrototype); //  类原型对象变量   URLInfo { show: [Function] }
    console.log('方法名:', methodname); //key
  };
}

function paramDecorator(
  targetClassPrototype: any,
  paramname: string,
  paramindex: number
) {
  console.log('=============执行参数装饰器==============');
  console.log('targetClassPrototype:', targetClassPrototype);
  console.log('参数名:', paramname);
  console.log('参数索引:', paramindex);
}

function UrlPropDecorator(targetClassPrototype: any, attrname: any) {
  console.log('=============执行属性装饰器==============');
  console.log('targetClassPrototype:', targetClassPrototype);
  console.log('属性名:', attrname);
}

function URLInfoDecorator(targetClassPrototype: any) {
  console.log('==========类装饰器============');
  console.log('targetClassPrototype:', targetClassPrototype);
}

function constructorDecorator(params: any) {
  return function (
    targetClassPrototype: any,
    paramname: string,
    paramindex: number
  ) {
    console.log('==========构造器参数装饰器============');
    console.log('构造器参数装饰器', targetClassPrototype);
    console.log('构造器参数名为:', paramname);
    console.log('构造器参数索引位置:', paramindex);
  };
}

@URLInfoDecorator
class URLInfo {
  constructor(@constructorDecorator('url') public uri: string) {}

  @UrlPropDecorator
  public url: string = 'https://www.imooc.com';

  @firstMethodDecorator
  methodOne(@paramDecorator data: string) {
    console.log('this:', this);
    console.log('目标类:', this.uri);
  }

  @secondMethodDecorator('yes')
  methodTwo(@paramDecorator address: string) {
    console.log(address);
  }
}
export {};


// =============执行属性装饰器==============
// targetClassPrototype: {}
// 属性名: url
// =============执行参数装饰器==============
// targetClassPrototype: {}
// 参数名: methodOne
// 参数索引: 0
// =============执行第一个方法装饰器==============
// 类名: {}
// 方法名: methodOne
// =============执行参数装饰器==============
// targetClassPrototype: {}
// 参数名: methodTwo
// 参数索引: 0
// =============执行第二个方法装饰器==============
// 类名: {}
// 方法名: methodTwo
// ==========构造器参数装饰器============
// 构造器参数装饰器 [class URLInfo]
// 构造器参数名为: undefined
// 构造器参数索引位置: 0
// ==========类装饰器============
// targetClassPrototype: [class URLInfo]

结论

  1. 原型属性装饰器
  2. 第一个方法参数装饰器
  3. 第一个方法装饰器
  4. 第二个方法参数装饰器
  5. 第二个方法装饰器
  6. ….
  7. 静态属性装饰器
  8. 构造器参数装饰器
  9. 类装饰器

类型上的顺序是:属性>方法参数>方法>类