13.1 装饰器是什么

  • 它是一个表达式
  • 该表达式被执行后,返回一个函数
  • 函数的入参分别为 target、name 和 descriptor
  • 执行该函数后,可能返回 descriptor 对象,用于配置 target 对象

    13.2 装饰器的分类

  • 类装饰器(Class decorators)

  • 属性装饰器(Property decorators)
  • 方法装饰器(Method decorators)
  • 参数装饰器(Parameter decorators)

    13.3 类装饰器

    类装饰器声明:

    1. declare type ClassDecorator = <TFunction extends Function>(
    2. target: TFunction
    3. ) => TFunction | void;

    类装饰器顾名思义,就是用来装饰类的。它接收一个参数:

  • target: TFunction - 被装饰的类

看完第一眼后,是不是感觉都不好了。没事,我们马上来个例子:

  1. function Greeter(target: Function): void {
  2. target.prototype.greet = function (): void {
  3. console.log("Hello Semlinker!");
  4. };
  5. }
  6. @Greeter
  7. class Greeting {
  8. constructor() {
  9. // 内部实现
  10. }
  11. }
  12. let myGreeting = new Greeting();
  13. myGreeting.greet(); // console output: 'Hello Semlinker!';

上面的例子中,我们定义了 Greeter 类装饰器,同时我们使用了 @Greeter 语法糖,来使用装饰器。

友情提示:读者可以直接复制上面的代码,在 TypeScript Playground 中运行查看结果。

有的读者可能想问,例子中总是输出 Hello Semlinker! ,能自定义输出的问候语么 ?这个问题很好,答案是可以的。
具体实现如下:

  1. function Greeter(greeting: string) {
  2. return function (target: Function) {
  3. target.prototype.greet = function (): void {
  4. console.log(greeting);
  5. };
  6. };
  7. }
  8. @Greeter("Hello TS!")
  9. class Greeting {
  10. constructor() {
  11. // 内部实现
  12. }
  13. }
  14. let myGreeting = new Greeting();
  15. myGreeting.greet(); // console output: 'Hello TS!';

13.4 属性装饰器

属性装饰器声明:

  1. declare type PropertyDecorator = (target:Object,
  2. propertyKey: string | symbol ) => void;

属性装饰器顾名思义,用来装饰类的属性。它接收两个参数:

  • target: Object - 被装饰的类
  • propertyKey: string | symbol - 被装饰类的属性名

趁热打铁,马上来个例子热热身:

  1. function logProperty(target: any, key: string) {
  2. delete target[key];
  3. const backingField = "_" + key;
  4. Object.defineProperty(target, backingField, {
  5. writable: true,
  6. enumerable: true,
  7. configurable: true
  8. });
  9. // property getter
  10. const getter = function (this: any) {
  11. const currVal = this[backingField];
  12. console.log(`Get: ${key} => ${currVal}`);
  13. return currVal;
  14. };
  15. // property setter
  16. const setter = function (this: any, newVal: any) {
  17. console.log(`Set: ${key} => ${newVal}`);
  18. this[backingField] = newVal;
  19. };
  20. // Create new property with getter and setter
  21. Object.defineProperty(target, key, {
  22. get: getter,
  23. set: setter,
  24. enumerable: true,
  25. configurable: true
  26. });
  27. }
  28. class Person {
  29. @logProperty
  30. public name: string;
  31. constructor(name : string) {
  32. this.name = name;
  33. }
  34. }
  35. const p1 = new Person("semlinker");
  36. p1.name = "kakuqo";

以上代码我们定义了一个 logProperty 函数,来跟踪用户对属性的操作,当代码成功运行后,在控制台会输出以下结果:

  1. Set: name => semlinker
  2. Set: name => kakuqo

13.5 方法装饰器

方法装饰器声明:

  1. declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,
  2. descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;

方法装饰器顾名思义,用来装饰类的方法。它接收三个参数:

  • target: Object - 被装饰的类
  • propertyKey: string | symbol - 方法名
  • descriptor: TypePropertyDescript - 属性描述符

废话不多说,直接上例子:

  1. function LogOutput(tarage: Function, key: string, descriptor: any) {
  2. let originalMethod = descriptor.value;
  3. let newMethod = function(...args: any[]): any {
  4. let result: any = originalMethod.apply(this, args);
  5. if(!this.loggedOutput) {
  6. this.loggedOutput = new Array<any>();
  7. }
  8. this.loggedOutput.push({
  9. method: key,
  10. parameters: args,
  11. output: result,
  12. timestamp: new Date()
  13. });
  14. return result;
  15. };
  16. descriptor.value = newMethod;
  17. }
  18. class Calculator {
  19. @LogOutput
  20. double (num: number): number {
  21. return num * 2;
  22. }
  23. }
  24. let calc = new Calculator();
  25. calc.double(11);
  26. // console ouput: [{method: "double", output: 22, ...}]
  27. console.log(calc.loggedOutput);

下面我们来介绍一下参数装饰器。

13.6 参数装饰器

参数装饰器声明:

  1. declare type ParameterDecorator = (target: Object, propertyKey: string | symbol,
  2. parameterIndex: number ) => void

参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:

  • target: Object - 被装饰的类
  • propertyKey: string | symbol - 方法名
  • parameterIndex: number - 方法中参数的索引值
    1. function Log(target: Function, key: string, parameterIndex: number) {
    2. let functionLogged = key || target.prototype.constructor.name;
    3. console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
    4. been decorated`);
    5. }
    6. class Greeter {
    7. greeting: string;
    8. constructor(@Log phrase: string) {
    9. this.greeting = phrase;
    10. }
    11. }
    12. // console output: The parameter in position 0
    13. // at Greeter has been decorated
    介绍完 TypeScript 入门相关的基础知识,猜测很多刚入门的小伙伴已有 “从入门到放弃” 的想法,最后我们来简单介绍一下编译上下文。