Decorator

Decorator 的概念来源于 Python 的 Decorator,也类似于 Java 的 Annotation。它实际上是一个 wrapper ,作用于一个函数或者对象,对目标函数或对象进行一系列变换,然后返回一个新的函数或对象。

其本质是:函数变换。一个 decorator 是一个函数,它有三个参数,第一个是被 decorator 修饰的对象,第二个是属性名,第三个参数是 Object.defineProperty 的 descriptor,有了这些参数,我们就可以在定义时,对它进行各种 “修饰”。


ES7 property decorator 是一种语法糖,它可以用更优雅的方式实现同样的函数变换(局限在 Class 中)。

  1. function deprecate(target, key, descriptor){
  2. console.log("[Function " + key + "] has been deprecated.");
  3. return descriptor;
  4. }
  5. function delay(ms){
  6. return function(target, key, descriptor){
  7. let func = target[key];
  8. descriptor.value = function(...args){
  9. return setTimeout(function(){
  10. func.apply(target, args);
  11. }, ms);
  12. }
  13. }
  14. }
  15. class MyClass {
  16. @deprecate
  17. old_method(){
  18. console.log("I\"m deprecated");
  19. }
  20. @delay(3000)
  21. new_method(){
  22. console.log("I\"m called after 3000ms");
  23. }
  24. }
  25. var a = new MyClass();
  26. a.new_method();

Evaluation

There is a well defined order to how decorators applied to various declarations inside of a class are applied:

  • Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each instance member.
  • Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each static member.
  • Parameter Decorators are applied for the constructor.
  • Class Decorators are applied for the class.

    Types of decorator

// Class decorator
function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    reportingURL = "http://www...";
  };
}

@reportableClassDecorator
class BugReport {
  type = "report";
  title: string;

  constructor(t: string) {
    this.title = t;
  }
}

// Method & Accessor decorator
class Greeter {

  msg = '';
  @enumerable(true)
  get greeting() { return this.msg };
  constructor(message: string) {
    this.msg = message;
  }

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting();
  }
}

function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

// Property decorator
import "reflect-metadata";
const formatMetadataKey = Symbol("format");

function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
  @format("Hello, %s")
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    let formatString = getFormat(this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}

// Parameter decorator
class BugReport {
  type = "report";
  title: string;

  constructor(t: string) {
    this.title = t;
  }

  @validate
  print(@required verbose: boolean) {
    if (verbose) {
      return `type: ${this.type}\ntitle: ${this.title}`;
    } else {
     return this.title; 
    }
  }
}

import "reflect-metadata";
const requiredMetadataKey = Symbol("required");

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata( requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
  let method = descriptor.value!;
  descriptor.value = function () {
    let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
    if (requiredParameters) {
      for (let parameterIndex of requiredParameters) {
        if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
          throw new Error("Missing required argument.");
        }
      }
    }
    return method.apply(this, arguments);
  };
}

ExecutionSequence

function classDecorator() {
  return (target: any) => {
    console.log("class decorator execute", target);
  };
}

function constrcutorParameterDecorator() {
  return (...args: any[]) => {
    console.log("constructor parameter decorator execute", ...args);
  };
}

function functionParameterDecorator() {
  return (...args: any[]) => {
    console.log("function parameter decorator execute", ...args);
  };
}

function methodDecorator() {
  return (...args: any[]) => {
    console.log("method decorator execute", ...args);
  };
}

function propertyDecorator() {
  return (...args: any[]) => {
    console.log("property decorator execute", ...args);
  };
}

@classDecorator()
class DeocratorDemo {
  @propertyDecorator()
  public greet: string;

  constructor(@constrcutorParameterDecorator() private test: string) {
    console.log(test);
    this.greet = test;
  }

  @methodDecorator()
  getGreeting(@functionParameterDecorator() newGreetName?: string) {
    console.log(newGreetName);
  }
}

new DeocratorDemo("hello").getGreeting();

Screen Shot 2021-11-02 at 3.24.40 PM.png
Typescript compiler 编译后的结果

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
function classDecorator() {
    return (target) => {
        console.log("class decorator execute", target);
    };
}
function constrcutorParameterDecorator() {
    return (...args) => {
        console.log("constructor parameter decorator execute", ...args);
    };
}
function functionParameterDecorator() {
    return (...args) => {
        console.log("function parameter decorator execute", ...args);
    };
}
function methodDecorator() {
    return (...args) => {
        console.log("method decorator execute", ...args);
    };
}
function propertyDecorator() {
    return (...args) => {
        console.log("property decorator execute", ...args);
    };
}
let DeocratorDemo = class DeocratorDemo {
    constructor(test) {
        this.test = test;
        console.log(test);
        this.greet = test;
    }
    getGreeting(newGreetName) {
        console.log(newGreetName);
    }
};
__decorate([
    propertyDecorator(),
    __metadata("design:type", String)
], DeocratorDemo.prototype, "greet", void 0);
__decorate([
    methodDecorator(),
    __param(0, functionParameterDecorator()),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", void 0)
], DeocratorDemo.prototype, "getGreeting", null);
DeocratorDemo = __decorate([
    classDecorator(),
    __param(0, constrcutorParameterDecorator()),
    __metadata("design:paramtypes", [String])
], DeocratorDemo);
new DeocratorDemo("hello").getGreeting();
const A = DeocratorDemo;