鍍金池/ 教程/ HTML/ 介紹
初始化項(xiàng)目結(jié)構(gòu)
聯(lián)合類型
介紹
介紹
介紹
編譯選項(xiàng)
TypeScript 1.6
介紹
介紹
發(fā)展路線圖
介紹
在MSBuild里使用編譯選項(xiàng)
可迭代性
TypeScript 1.3
介紹
介紹
TypeScript 1.1
變量聲明
即將到來(lái)的Angular 2框架是使用TypeScript開發(fā)的。 因此Angular和TypeScript一起使用非常簡(jiǎn)單方便
tsconfig.json
介紹
介紹
介紹
在MSBuild里使用編譯選項(xiàng)
使用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)建簡(jiǎn)單工程
TypeScript 1.7
TypeScript 1.5
NPM包的類型
支持TypeScript的編輯器

介紹

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

注意  Decorators是實(shí)驗(yàn)性的特性,在未來(lái)的版本中可能會(huì)發(fā)生改變。

若要啟用實(shí)驗(yàn)性的decorator,你必須啟用experimentalDecorators編譯器選項(xiàng),在命令行中或在tsconfig.json

命令行:

tsc --target ES5 --experimentalDecorators

tsconfig.json:

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

Decorators (后文譯作裝飾器)

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

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

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

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

裝飾器工廠

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

我們可以通過(guò)下面的方式來(lái)寫一個(gè)裝飾器工廠

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

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

裝飾器組合

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

  • 寫在同一行上:

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

    @f
    @g
    x

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

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

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

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

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() {}
}

在控制臺(tái)里會(huì)打印出如下結(jié)果:

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

裝飾器求值

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

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

類裝飾器

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

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

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

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

下面是使用類裝飾器(@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í)行的時(shí)候,它將密封此類的構(gòu)造函數(shù)和原型。(注:參見(jiàn)Object.seal)

方法裝飾器

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

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

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

注意  如果代碼輸出目標(biāo)版本小于ES5Property Descriptor將會(huì)是undefined。

如果方法裝飾器返回一個(gè)值,它會(huì)被用作方法的屬性描述符

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

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

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

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

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

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

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

訪問(wèn)符裝飾器

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

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

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

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

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

如果訪問(wèn)符裝飾器返回一個(gè)值,它會(huì)被用作方法的屬性描述符

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

下面是使用了訪問(wèn)符裝飾器(@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; }
}

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

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

屬性裝飾器

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

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

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

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

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

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

如果訪問(wèn)符裝飾器返回一個(gè)值,它會(huì)被用作方法的屬性描述符。

我們可以用它來(lái)記錄這個(gè)屬性的元數(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);
}

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

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

參數(shù)裝飾器

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

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

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

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

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

下例定義了參數(shù)裝飾器(@required)并應(yīng)用于Greeter類方法的一個(gè)參數(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í)體把參數(shù)標(biāo)記為必須的。 @validate裝飾器把greet方法包裹在一個(gè)函數(shù)里在調(diào)用原先的函數(shù)前驗(yàn)證函數(shù)參數(shù)。

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

元數(shù)據(jù)

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

你可以通過(guò)npm安裝這個(gè)庫(kù):

npm i reflect-metadata --save

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

Command Line:

tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata

tsconfig.json:

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

當(dāng)啟用后,只要reflect-metadata庫(kù)被引入了,設(shè)計(jì)階段額外的信息可以在運(yùn)行時(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編譯器可以通過(guò)@Reflect.metadata裝飾器注入設(shè)計(jì)階段的類型信息。 你可以認(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ù)是個(gè)實(shí)驗(yàn)性的特性并且可能在以后的版本中發(fā)生破壞性的改變(breaking changes)。

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