Decorator
Decorator 的概念来源于 Python 的 Decorator,也类似于 Java 的 Annotation。它实际上是一个 wrapper ,作用于一个函数或者对象,对目标函数或对象进行一系列变换,然后返回一个新的函数或对象。
其本质是:函数变换。一个 decorator
是一个函数,它有三个参数,第一个是被 decorator 修饰的对象,第二个是属性名,第三个参数是 Object.defineProperty 的 descriptor
,有了这些参数,我们就可以在定义时,对它进行各种 “修饰”。
ES7 property decorator 是一种语法糖,它可以用更优雅的方式实现同样的函数变换(局限在 Class
中)。
function deprecate(target, key, descriptor){
console.log("[Function " + key + "] has been deprecated.");
return descriptor;
}
function delay(ms){
return function(target, key, descriptor){
let func = target[key];
descriptor.value = function(...args){
return setTimeout(function(){
func.apply(target, args);
}, ms);
}
}
}
class MyClass {
@deprecate
old_method(){
console.log("I\"m deprecated");
}
@delay(3000)
new_method(){
console.log("I\"m called after 3000ms");
}
}
var a = new MyClass();
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();
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;