Contents

JS装饰器(Decorator)

概念

装饰器是 ES7 的一个新语法,在Typescript已经支持。作用就是**对一些对象进行装饰后返回一个被包装过的对象。**装饰器本质是基于Object.defineProperty 的语法糖,参数也与 Object.defineProperty相同,即:

参数名 描述
target 目标对象
name 属性名
descriptor 针对该属性的描述符

可以被应用在:

  1. 类属性
  2. 类方法
  3. 类访问器
  4. 类方法的参数
// 类装饰器
@classDecorator
class Bird {

  // 属性装饰器
  @propertyDecorator
  name: string;
  
  // 方法装饰器
  @methodDecorator
  fly(
    // 参数装饰器
    @parameterDecorator
      meters: number
  ) {}
  
  // 访问器装饰器
  @accessorDecorator
  get egg() {}
}

执行

时机

装饰器只在解释执行时应用一次,例如:

function f(C) {
  console.log('apply decorator')
  return C
}

@f
class A {}

// output: apply decorator
// 即便没有使用类A,也会打印`apply decorator`

执行顺序

对于属性/方法/访问器装饰器而言,执行顺序取决于声明它们的顺序。

而对于参数装饰器来说执行顺序是相反的, 最后一个参数的装饰器会最先被执行。

当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用.

依次为:

  1. 从上往下,谁先写谁执行。
  2. 先实例属性,后静态属性。
  3. 先参数装饰器再方法装饰器,而且会在一起执行。
  4. 最后类装饰器。
function f(key: string): any {
  console.log("evaluate: ", key);
  return function () {
    console.log("call: ", key);
  };
}

@f("Class Decorator")
class C {
  @f("Static Property")
  static prop?: number;

  @f("Static Method")
  static method(@f("Static Method Parameter") foo) { }

  constructor(@f("Constructor Parameter") foo) { }

  @f("Instance Method Outer ")
  @f("Instance Method Inner ")
  method(@f("Instance Method Parameter") foo) { }

  @f("Instance Property")
  prop?: number;
}


// evaluate:  Instance Method Outer 
// evaluate:  Instance Method Inner 
// evaluate:  Instance Method Parameter
// call:  Instance Method Parameter
// call:  Instance Method Inner 
// call:  Instance Method Outer 
// evaluate:  Instance Property
// call:  Instance Property
// evaluate:  Static Property
// call:  Static Property
// evaluate:  Static Method
// evaluate:  Static Method Parameter
// call:  Static Method Parameter
// call:  Static Method
// evaluate:  Class Decorator
// evaluate:  Constructor Parameter
// call:  Constructor Parameter
// call:  Class Decorator

用法

类装饰器

类型

type ClassDecorator = <TFunction extends Function>
  (target: TFunction) => TFunction | void;

参数

参数名 描述
target 类的构造器

返回

如果类装饰器返回了一个值,她将会被用来代替原有的类构造器的声明。

应用场景

类装饰器适合用于继承一个现有类并添加一些属性和方法。

type Consturctor = { new (...args: any[]): any };

function toString<T extends Consturctor>(BaseClass: T) {
  return class extends BaseClass {
    toString() {
      return JSON.stringify(this);
    }
  };
}

@toString
class C {
  public foo = "foo";
  public num = 24;
}

console.log(new C().toString())
// -> {"foo":"foo","num":24}

类属性装饰器

参数

参数名 描述
target 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链。
name 属性名

返回

返回值会被忽略

应用场景

  • 收集信息
  • 给类添加额外的方法和属性

function observable(target: any, key: string): void {
  let val = target[key];
  Reflect.defineProperty(target, key, {
    get() {
      console.log('拦截到正在获取属性:' + key);
      return val
    },
    set(next) {
      console.log(`prev:${val},next:${next}`);
      val = next
    }
  })
}

class C {
  @observable
  foo = -1; // prev: undefined, next: -1

  @observable
  bar = "bar"; // prev: undefined, next: bar
}

const c = new C();

c.foo = 100; // -> prev: -1, next: 100
c.foo = -3.14; // -> prev: 100, next: -3.14
c.bar = "baz"; // -> prev: bar, next: baz
c.bar = "sing"; // -> prev: baz, next: sing
console.log(c.bar); // -> 拦截到正在获取属性:bar
console.log(c.foo); // -> 拦截到正在获取属性:foo
console.log(c) // 不会输出,只有target[key]的情况下,defineProperty才会拦截到

类方法/访问器装饰器

参数

参数名 描述
target 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链。
propertyKey 属性名称
descriptor 属性的描述器

访问器装饰器唯一的区别在于描述器:

方法装饰器的描述器的key为:

  • value
  • writable
  • enumerable
  • configurable

访问器装饰器的描述器的key为:

  • get
  • set
  • enumerable
  • configurable

返回值

如果返回了值,它会被用于替代属性的描述器。

应用场景

方法装饰器不同于属性装饰器的地方在于descriptor参数。 通过这个参数可以修改方法原本的实现,添加一些共用逻辑。

例如:

  1. 增加消除繁琐的try/catch块,装饰器内统一输出函数日志。
  2. 校验参数或返回值类型

以打印输入与输出举例:

function logger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args) {
    console.log('params: ', ...args);
    const result = original.call(this, ...args);
    console.log('result: ', result);
    return result;
  }
}

class C {
  @logger
  add(x: number, y: number) {
    return x + y;
  }
}

const c = new C();
c.add(1, 2);
// -> params: 1, 2
// -> result: 3

类参数装饰器

类型

type ParameterDecorator = (
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) => void;

参数

参数名 描述
target 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链。
name 属性的名称(注意是方法的名称,而不是参数的名称)。
index 参数在方法中所处的位置的下标。

返回

返回的值将会被忽略。

应用场景

只能做一些参数的记录,不能更改参数相关的任何内容,(除非配合reflect-metadata),一般需要配合其他的装饰器使用。例如配合方法装饰器进行运行时的参数检查。

装饰器工厂

如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再函数一个函数即可,如下:

function addAge(name: string) {
  return function (constructor: Function) {
    constructor.prototype.name = name;
  }
}

@addAge('bhwa233')
class Person {
  name: string;
}

let person = new Person()

console.log(person.name); //bhwa233

参考链接:

typescript官方文档中的Decorators

TypeScript装饰器完全指南