鍍金池/ 教程/ HTML/ 介紹
初始化項目結(jié)構(gòu)
聯(lián)合類型
介紹
介紹
介紹
編譯選項
TypeScript 1.6
介紹
介紹
發(fā)展路線圖
介紹
在MSBuild里使用編譯選項
可迭代性
TypeScript 1.3
介紹
介紹
TypeScript 1.1
變量聲明
即將到來的Angular 2框架是使用TypeScript開發(fā)的。 因此Angular和TypeScript一起使用非常簡單方便
tsconfig.json
介紹
介紹
介紹
在MSBuild里使用編譯選項
使用TypeScript的每日構(gòu)建版本
新建工程
枚舉
三斜線指令
結(jié)合ASP.NET v5使用TypeScript
TypeScript里的this
介紹
TypeScript 1.4
編碼規(guī)范
介紹
模塊解析
ASP.NET 4
架構(gòu)概述
介紹
介紹
ASP.NET Core
TypeScript 1.8
介紹
介紹
創(chuàng)建簡單工程
TypeScript 1.7
TypeScript 1.5
NPM包的類型
支持TypeScript的編輯器

介紹

隨著TypeScript和ES6里引入了類,現(xiàn)在在一些場景下我們會需要額外的特性,用來支持標(biāo)注或修改類及其成員。 Decorators提供了一種在類的聲明和成員上使用元編程語法添加標(biāo)注的方式。 Javascript里的Decorators目前處在建議征集的第一階段,在TypeScript里做為實驗性特性已經(jīng)提供了支持。

注意  Decorators是實驗性的特性,在未來的版本中可能會發(fā)生改變。

若要啟用實驗性的decorator,你必須啟用experimentalDecorators編譯器選項,在命令行中或在tsconfig.json

命令行:

tsc --target ES5 --experimentalDecorators

tsconfig.json:

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

Decorators (后文譯作裝飾器)

裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明方法,訪問符屬性,或 參數(shù)上。 裝飾器利用@expression這種方式,expression求值后必須為一個函數(shù),它使用被裝飾的聲明信息在運行時被調(diào)用。

例如,有一個@sealed裝飾器,我們會這樣定義sealed函數(shù):

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

注意  下面類裝飾器小節(jié)里有一個更加詳細的例子。

裝飾器工廠

如果我們想自定義裝飾器是如何作用于聲明的,我們得寫一個裝飾器工廠函數(shù)。 裝飾器工廠就是一個簡單的函數(shù),它返回一個表達式,以供裝飾器在運行時調(diào)用。

我們可以通過下面的方式來寫一個裝飾器工廠

function color(value: string) { // 這是一個裝飾器工廠
    return function (target) { //  這是裝飾器
        // do something with "target" and "value"...
    }
}

注意  下面方法裝飾器小節(jié)里有一個更加詳細的例子。

裝飾器組合

多個裝飾器可以同時應(yīng)用到一個聲明上,就像下面的示例:

  • 寫在同一行上:

    @f @g x
  • 寫在多行上:

    @f
    @g
    x

當(dāng)多個裝飾器應(yīng)用于一個聲明上,它們求值方式與復(fù)合函數(shù)相似。在這個模型下,當(dāng)復(fù)合fg時,復(fù)合的結(jié)果(f ° g)(x)等同于f(g(x))。

同樣的,在TypeScript里,當(dāng)多個裝飾器應(yīng)用在一個聲明上時會進行如下步驟的操作:

  1. 由上至下依次對裝飾器表達式求值。
  2. 求值的結(jié)果會被當(dāng)作函數(shù),由下至上依次調(diào)用。

如果我們使用裝飾器工廠的話,可以通過下面的例子來觀察它們求值的順序:

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

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

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

在控制臺里會打印出如下結(jié)果:

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

裝飾器求值

類中不同聲明上的裝飾器將按以下規(guī)定的順序應(yīng)用:

  1. 參數(shù)裝飾器,其次是方法,訪問符,或屬性裝飾器應(yīng)用到每個實例成員。
  2. 參數(shù)裝飾器,其次是方法訪問符,或屬性裝飾器應(yīng)用到每個靜態(tài)成員。
  3. 參數(shù)裝飾器應(yīng)用到構(gòu)造函數(shù)。
  4. 類裝飾器應(yīng)用到類。

類裝飾器

類裝飾器在類聲明之前被聲明(緊貼著類聲明)。 類裝飾器應(yīng)用于類構(gòu)造函數(shù),可以用來監(jiān)視,修改或替換類定義。 類裝飾器不能用在聲明文件中(.d.ts),也不能用在任何外部上下文中(比如declare的類)。

類裝飾器表達式會在運行時當(dāng)作函數(shù)被調(diào)用,類的構(gòu)造函數(shù)作為其唯一的參數(shù)。

如果類裝飾器返回一個值,它會使用提供的構(gòu)造函數(shù)來替換類的聲明。

注意  如果你要返回一個新的構(gòu)造函數(shù),你必須注意處理好原來的原型鏈。 在運行時的裝飾器調(diào)用邏輯中不會為你做這些。

下面是使用類裝飾器(@sealed)的例子,應(yīng)用到Greeter類:

@sealed
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

我們可以這樣定義@sealed裝飾器

function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

當(dāng)@sealed被執(zhí)行的時候,它將密封此類的構(gòu)造函數(shù)和原型。(注:參見Object.seal)

方法裝飾器

方法裝飾器聲明在一個方法的聲明之前(緊貼著方法聲明)。 它會被應(yīng)用到方法的屬性描述符上,可以用來監(jiān)視,修改或者替換方法定義。 方法裝飾器不能用在聲明文件(.d.ts),重載或者任何外部上下文(比如declare的類)中。

方法裝飾器表達式會在運行時當(dāng)作函數(shù)被調(diào)用,傳入下列3個參數(shù):

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象。
  2. 成員的名字。
  3. 成員的屬性描述符。

注意  如果代碼輸出目標(biāo)版本小于ES5,Property Descriptor將會是undefined

如果方法裝飾器返回一個值,它會被用作方法的屬性描述符。

注意  如果代碼輸出目標(biāo)版本小于ES5返回值會被忽略。

下面是一個方法裝飾器(@enumerable)的例子,應(yīng)用于Greeter類的方法上:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }

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

我們可以用下面的函數(shù)聲明來定義@enumerable裝飾器:

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

這里的@enumerable(false)是一個裝飾器工廠。 當(dāng)裝飾器@enumerable(false)被調(diào)用時,它會修改屬性描述符的enumerable屬性。

訪問符裝飾器

訪問符裝飾器聲明在一個訪問符的聲明之前(緊貼著訪問符聲明)。 訪問符裝飾器應(yīng)用于訪問符的屬性描述符并且可以用來監(jiān)視,修改或替換一個訪問符的定義。 訪問符裝飾器不能用在聲明文件中(.d.ts),或者任何外部上下文(比如declare的類)里。

注意  TypeScript不允許同時裝飾一個成員的getset訪問符。相反,所有裝飾的成員必須被應(yīng)用到文檔順序指定的第一個訪問符。這是因為,裝飾器應(yīng)用于一個屬性描述符,它聯(lián)合了getset訪問符,而不是分開聲明的。

訪問符裝飾器表達式會在運行時當(dāng)作函數(shù)被調(diào)用,傳入下列3個參數(shù):

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象。
  2. 成員的名字。
  3. 成員的屬性描述符

注意  如果代碼輸出目標(biāo)版本小于ES5,Property Descriptor將會是undefined

如果訪問符裝飾器返回一個值,它會被用作方法的屬性描述符

注意  如果代碼輸出目標(biāo)版本小于ES5返回值會被忽略。

下面是使用了訪問符裝飾器(@configurable)的例子,應(yīng)用于Point類的成員上:

class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() { return this._x; }

    @configurable(false)
    get y() { return this._y; }
}

我們可以通過如下函數(shù)聲明來定義@configurable裝飾器:

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

屬性裝飾器

屬性裝飾器聲明在一個屬性聲明之前(緊貼著屬性聲明)。 屬性裝飾器不能用在聲明文件中(.d.ts),或者任何外部上下文(比如declare的類)里。

屬性裝飾器表達式會在運行時當(dāng)作函數(shù)被調(diào)用,傳入下列2個參數(shù):

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象。
  2. 成員的名字。

注意  屬性描述符不會做為參數(shù)傳入屬性裝飾器,這與TypeScript是如何初始化屬性裝飾器的有關(guān)。 因為目前沒有辦法在定義一個原型對象的成員時描述一個實例屬性,并且沒辦法監(jiān)視或修改一個屬性的初始化方法。 因此,屬性描述符只能用來監(jiān)視類中是否聲明了某個名字的屬性。

如果屬性裝飾器返回一個值,它會被用作方法的屬性描述符。

注意  如果代碼輸出目標(biāo)版本小于ES5,返回值會被忽略。

如果訪問符裝飾器返回一個值,它會被用作方法的屬性描述符。

我們可以用它來記錄這個屬性的元數(shù)據(jù),如下例所示:

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);
    }
}

然后定義@format裝飾器和getFormat函數(shù):

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);
}

這個 @format("Hello, %s") 裝飾器是個 裝飾器工廠。 當(dāng)@format("Hello, %s")被調(diào)用時,它添加一條這個屬性的元數(shù)據(jù),通過reflect-metadata庫里的Reflect.metadata函數(shù)。 當(dāng)getFormat被調(diào)用時,它讀取格式的元數(shù)據(jù)。

注意  這個例子需要使用reflect-metadata庫。 查看元數(shù)據(jù)了解reflect-metadata庫更詳細的信息。

參數(shù)裝飾器

參數(shù)裝飾器聲明在一個參數(shù)聲明之前(緊貼著參數(shù)聲明)。 參數(shù)裝飾器應(yīng)用于類構(gòu)造函數(shù)或方法聲明。 參數(shù)裝飾器不能用在聲明文件(.d.ts),重載或其它外部上下文(比如declare的類)里。

參數(shù)裝飾器表達式會在運行時當(dāng)作函數(shù)被調(diào)用,傳入下列3個參數(shù):

  1. 對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實例成員是類的原型對象。
  2. 成員的名字。
  3. 參數(shù)在函數(shù)參數(shù)列表中的索引。

注意  參數(shù)裝飾器只能用來監(jiān)視一個方法的參數(shù)是否被傳入。

參數(shù)裝飾器的返回值會被忽略。

下例定義了參數(shù)裝飾器(@required)并應(yīng)用于Greeter類方法的一個參數(shù):

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @validate
    greet(@required name: string) {
        return "Hello " + name + ", " + this.greeting;
    }
}

然后我們使用下面的函數(shù)定義 @required@validate 裝飾器:

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);
    }
}

@required裝飾器添加了元數(shù)據(jù)實體把參數(shù)標(biāo)記為必須的。 @validate裝飾器把greet方法包裹在一個函數(shù)里在調(diào)用原先的函數(shù)前驗證函數(shù)參數(shù)。

注意  這個例子使用了reflect-metadata庫。 查看元數(shù)據(jù)了解reflect-metadata庫的更多信息。

元數(shù)據(jù)

一些例子使用了reflect-metadata庫來支持實驗性的 metadata API。 這個庫還不是ECMAScript (JavaScript)標(biāo)準(zhǔn)的一部分。 然而,當(dāng)裝飾器被ECMAScript官方標(biāo)準(zhǔn)采納后,這些擴展也將被推薦給ECMAScript以采納。

你可以通過npm安裝這個庫:

npm i reflect-metadata --save

TypeScript支持為帶有裝飾器的聲明生成元數(shù)據(jù)。 你需要在命令行或tsconfig.json里啟用emitDecoratorMetadata編譯器選項。

Command Line:

tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata

tsconfig.json:

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

當(dāng)啟用后,只要reflect-metadata庫被引入了,設(shè)計階段額外的信息可以在運行時使用。

如下例所示:

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.");
        }
    }
}

TypeScript編譯器可以通過@Reflect.metadata裝飾器注入設(shè)計階段的類型信息。 你可以認(rèn)為它相當(dāng)于下面的TypeScript:

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

    @validate
    @Reflect.metadata("design:type", Point)
    set p0(value: Point) { this._p0 = value; }
    get p0() { return this._p0; }

    @validate
    @Reflect.metadata("design:type", Point)
    set p1(value: Point) { this._p1 = value; }
    get p1() { return this._p1; }
}

注意  裝飾器元數(shù)據(jù)是個實驗性的特性并且可能在以后的版本中發(fā)生破壞性的改變(breaking changes)。

上一篇:架構(gòu)概述下一篇:介紹