Skip to content

TS学习系列03 ---TypeScript装饰器

一、装饰器是什么?

装饰器是一种特殊类型的声明,本质上就是一个方法,可以注入到类、方法、属性、参数上,扩展其功能;

简单总结:

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

二、装饰器分类及作用

常见的装饰器:

  • 类装饰器 ClassDecorator
  • 属性装饰器 PropertyDecorator
  • 方法装饰器 MethodDecorator
  • 参数装饰器 ParameterDecorator

装饰器在写法上有:

  • 普通装饰器(无法传参)、
  • 装饰器工厂(可传参)

作用

  • 装饰器的目的就是为了给代码添加新的功能,随着程序的功能越来越多,需要给某一个小块添加上功能,这时可以使用装饰器,对类进行方法的新增操作,装饰器与ES6 中的面向对象的继承式有一定的区别的,但是本质上,他们都是在操作原型对象,通过给原型对象 prototype 添加一些方法和属性,来扩展类的功能

三、装饰器的定义及使用

定义:

  • 装饰器的定义,装饰器的本身,他是一个函数,会在运行的时候被调用,被装饰的类或函数或其他,会作为参数传递给装饰器函数,当作形参。

使用

  • 定义完成装饰器,通过 @ + 装饰器名称,将其书写在要装饰的类的声明上面,这样的含义就是,当前装饰器下面的类,会一参数的形式,传递给装饰器函数

四、ts中启用装饰器

若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项:

命令行:

ts
tsc --target ES5 --experimentalDecorators

tsconfig.json:

ts
{
  "compilerOptions": {
     "target": "ES5",
     "experimentalDecorators": true
   }
}

五、详细介绍

1、类装饰器ClassDecorator

ts类型: ClassDecorator 类装饰器在类声明之前被声明,应用于类构造函数,可以监视、修改、替换类的定义,传入一个参数 装饰器的本身,是一个函数,会在运行的时候被调用,被装饰的类,会作为参数传递给装饰器函数,当作形参

类装饰器接收一个构造函数作为参数,参数的类型是一个函数

ts
// 定义一个名叫Greeter的类装饰器 Greeter接收的参数target就是被装饰的类Greeting 
function Greeter(target: Function): void {
  target.prototype.greet = function (): void {
    console.log("Hello ClassDecorator!");
  };
}

@Greeter
class Greeting {
  constructor() {
    // 内部实现
  }
}

let myGreeting = new Greeting();
(myGreeting as any).greet(); // console output: 'Hello Semlinker!';

class装饰器传参

同样是上面的例子,每次执行类实例上的greet()方法都是输出“Hello ClassDecorator!”,要想动态输出其他的,可以通过传参的形式来实现。

ts
// 定义装饰器(接收一个参数,类型是string)
function Greeter(greeting: string) {
  // 装饰器函数返回一个函数,接收要被装饰的class作为参数target
  return function (target: Function) {
    target.prototype.greet = function (): void {
      console.log(greeting);// 动态的将床过来的参数打印出来
    };
  };
}

@Greeter("Hello TS!") // 将要打印的参数以参数的形式传给装饰器
class Greeting {
  constructor() {
    // 内部实现
  }
}

let myGreeting = new Greeting();
(myGreeting as any).greet(); // 输出: 'Hello TS!';

2、属性装饰器PropertyDecorator

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

  • target: Object - 被装饰的类
  • propertyKey: string | symbol - 被装饰类的属性名
ts
// 定义一个属性装饰器
const nameDecorator:PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
  // 对被装饰的属性进行劫持
  Object.defineProperty(target,propertyKey, {
    set (v) {
      console.log(v);
    }
  })
}

// 定义class
class User {
    // 对str属性使用定义好的属性装饰器
    @nameDecorator
    public  str: string;
  constructor(str: string) {
    this.str = str // 给属性赋值时候会触发装饰器内的set方法
  }
}
new User('小明') // 小明

3、方法装饰器

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

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

这里需要注意的事项就是,在装饰器里,使用的技巧就是先将原有的值进行存储一下,在去使用,确保它使用的是类中的方法中的值

ts
/**
 * 装饰器函数
 * @param target  - 被装饰的类
 * @param propertyKey  - 方法名
 * @param descriptor  - 属性描述符
 */
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  let originalMethod = descriptor.value; //被装饰的方法体,先存一份
  console.log(originalMethod === target[propertyKey]); // true
  descriptor.value = function (...args: any[]) {
    console.log("装饰器函数: 调用前 " + propertyKey); // 1
    let result = originalMethod.apply(this, args); // 2
    console.log("装饰器函数: 调用后 " + propertyKey); // 3

    return result; // 4
  };
}

class Task {
  @log
  runTask(arg: any): any {
    console.log("runTask函数 调用完成, args: " + arg);
    return "finished";
  }
}

let task = new Task();
let result = task.runTask("learn ts");
console.log("result: " + result); // result: finished

 // 输出结果
/**
装饰器函数: 调用前 runTask
runTask函数 调用完成, args: learn ts
装饰器函数: 调用后 runTask
result: finished
 */

4、参数装饰器

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

  • target: Object - 被装饰的类
  • propertyKey: string | symbol - 方法名
  • parameterIndex: number - 方法中参数的索引值
ts
function Log(target: Function, key: string, parameterIndex: number) {
  let functionLogged = key || target.prototype.constructor.name;
  // The parameter in position 1 at Greeter has been decorated
  console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has been decorated`);
}

class Greeter {
  greeting: string;
  constructor(a:string,@Log phrase: string) {
    this.greeting = phrase; 
  }
}

5、装饰器工厂

装饰器工厂的作用就是,根据使用装饰器的类,类给装饰器工程传递数据,根据数据返回不同的装饰器,这个函数被称之为 装饰器工厂

ts
  const musicDecoratorFactory = (str: string):ClassDecorator => {
    switch (str) {
      case 'Token':
      return (target: Function):any => {
        target.prototype.play = function():void {
          console.log('播放音乐' + str);
        }
      };
      default:
      return (target: Function):any => {
        target.prototype.play = function():void {
          console.log('播放音乐' + str);
        }
      };
    }
  }
  
  @musicDecoratorFactory('Token')
  class Music {
    public play() {}
  }

  new Music().play()

6、装饰器的叠加

装饰器不仅仅只能使用一个,可以定义多个装饰器,作用于一个类身上,通过叠加装饰器的方式,给类追加多个方法和属性

ts
  const moveDecortor:ClassDecorator = (target:Function):any => {
    target.prototype.forName = ():number => {
      return 1
    }
  }

  const musicDecortor: ClassDecorator = (target:Function):any => {
    target.prototype.block = ():void => {
      console.log('播放音乐');
    }
  }

  @moveDecortor
  @musicDecortor
  class User{
    public forName() {}
    public block() {}
  }
  const u = new User()
  console.log((u.forName())
  u.block()