Tiven Wang
Wang Tiven June 15, 2019
425 favorite favorites
bookmark bookmark
share share

自从有了 TypeScript 和 ES6 中的 Classes 就有了需求要求支持注解 (annotating) 或者修改 classes 或者 classes members 的功能。Decorators 正是提供了这种注解 (annotating) 和 meta-programming syntax for class declarations and members 的功能。Decorators 是 JavaScript 第二阶段的 Proposal,现在是 TypeScript 的 experimental feature, 可以通过 experimentalDecorators 参数启用此功能。

Decorators

Decorators 是可以附在 class declaration, method, accessor, property, or parameter 上的一种声明。Decorators 使用 @expression 形式书写,expression 等于一个可以在运行时被调用的函数名。例如你要有个 @sealed decorator, 那你就得有个函数是

function sealed(target) {
    // do something with 'target' ...
}

多个 Decorators 可以叠加使用,如

@f @g x
// or
@f
@g
x

那么这样在运行时就如同数学函数的组合调用一样,(f ∘ g)(x) 等于 f(g(x))

如果要更清晰地看到 Decorators 是怎么被调用的,可以使用下面这段代码

function f() {
    console.log("f(): evaluated");
    return function (target:any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target:any, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

执行命令 npx ts-node index.ts 可以看到输出

f(): evaluated
g(): evaluated
g(): called
f(): called

也可以编译成 ES5 看看编译后的代码跟深入地理解以下

"use strict";
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;
};
function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey, descriptor) {
        console.log("f(): called");
    };
}
function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey, descriptor) {
        console.log("g(): called");
    };
}
var C = /** @class */ (function () {
    function C() {
    }
    C.prototype.method = function () { };
    __decorate([
        f(),
        g()
    ], C.prototype, "method", null);
    return C;
}());

Class Decorators

那么对于 Class 级别的 Decorators 也可以通过下面这个例子做深入理解

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

现在很多引擎都已经支持 ES2015 了,所以对于 JavaScript 初学者就没必要再去理解老版 JavaScript 代码了,所以我们在 tsconfig.json 配置里改为 "target": "ES2015",这样生成的 ES2015 版代码如下

"use strict";
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;
};
function classDecorator(constructor) {
    return class extends constructor {
        constructor() {
            super(...arguments);
            this.newProperty = "new property";
            this.hello = "override";
        }
    };
}
let Greeter = class Greeter {
    constructor(m) {
        this.property = "property";
        this.hello = m;
    }
};
Greeter = __decorate([
    classDecorator
], Greeter);
console.log(new Greeter("world"));

Metadata

Metadata 相当于一个 Decorators 应用场景,目前也是 experimental 的功能,可以通过 emitDecoratorMetadata 参数启用。 Metadata 会通过 Decorators 的方式给 Target (class, method …) 添加 metadata (key-value) ,然后解析的地方又可以通过 Metadata 的 API 读取到 metadata 信息。

import "reflect-metadata";

class Point {
    x: number;
    y: number;
}

class Line {
    private _p0: Point;
    private _p1: Point;

    @validate
    set p0(value: Point) { this._p0 = value; }
    get p0() { return this._p0; }

    @validate
    set p1(value: Point) { this._p1 = value; }
    get p1() { return this._p1; }
}

function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) {
    let set = descriptor.set;
    descriptor.set = function (value: T) {
        let type = Reflect.getMetadata("design:type", target, propertyKey);
        if (!(value instanceof type)) {
            throw new TypeError("Invalid type.");
        }
        set.call(target, value);
    }
}

因为 TypeScript compiler 会为 p0 p1 添加 metaddata 所以我们可以在 validate 方法里通过 ` Reflect.getMetadata 获取到名为 design:type` 的 metadata 信息来使用。

References

Similar Posts

Comments

Back to Top