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

介紹

TypeScript中有些獨(dú)特的概念可以在類型層面上描述JavaScript對(duì)象的模型。 這其中尤其獨(dú)特的一個(gè)例子是“聲明合并”的概念。 理解了這個(gè)概念,將有助于操作現(xiàn)有的JavaScript代碼。 同時(shí),也會(huì)有助于理解更多高級(jí)抽象的概念。

對(duì)本文件來講,“聲明合并”是指編譯器將針對(duì)同一個(gè)名字的兩個(gè)獨(dú)立聲明合并為單一聲明。 合并后的聲明同時(shí)擁有原先兩個(gè)聲明的特性。 任何數(shù)量的聲明都可被合并;不局限于兩個(gè)聲明。

基礎(chǔ)概念

Typescript中的聲明會(huì)創(chuàng)建以下三種實(shí)體之一:命名空間,類型或值。 創(chuàng)建命名空間的聲明會(huì)新建一個(gè)命名空間,它包含了用(.)符號(hào)來訪問時(shí)使用的名字。 創(chuàng)建類型的聲明是:用聲明的模型創(chuàng)建一個(gè)類型并綁定到給定的名字上。 最后,創(chuàng)建值的聲明會(huì)創(chuàng)建在JavaScript輸出中看到的值。

Declaration Type Namespace Type Value
Namespace X X
Class X X
Enum X X
Interface X
Type Alias X
Function X
Variable X

理解每個(gè)聲明創(chuàng)建了什么,有助于理解當(dāng)聲明合并時(shí)有哪些東西被合并了。

合并接口

最簡單也最常見的聲明合并類型是接口合并。 從根本上說,合并的機(jī)制是把雙方的成員放到一個(gè)同名的接口里。

interface Box {
    height: number;
    width: number;
}

interface Box {
    scale: number;
}

let box: Box = {height: 5, width: 6, scale: 10};

接口的非函數(shù)的成員必須是唯一的。 如果兩個(gè)接口中同時(shí)聲明了同名的非函數(shù)成員編譯器則會(huì)報(bào)錯(cuò)。

對(duì)于函數(shù)成員,每個(gè)同名函數(shù)聲明都會(huì)被當(dāng)成這個(gè)函數(shù)的一個(gè)重載。 同時(shí)需要注意,當(dāng)接口A與后來的接口A合并時(shí),后面的接口具有更高的優(yōu)先級(jí)。

如下例所示:

interface Cloner {
    clone(animal: Animal): Animal;
}

interface Cloner {
    clone(animal: Sheep): Sheep;
}

interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
}

這三個(gè)接口合并成一個(gè)聲明:

interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
    clone(animal: Sheep): Sheep;
    clone(animal: Animal): Animal;
}

注意每組接口里的聲明順序保持不變,但各組接口之間的順序是后來的接口重載出現(xiàn)在靠前位置。

這個(gè)規(guī)則有一個(gè)例外是當(dāng)出現(xiàn)特殊的函數(shù)簽名時(shí)。 如果簽名里有一個(gè)參數(shù)的類型是單一的字符串字面量(比如,不是字符串字面量的聯(lián)合類型),那么它將會(huì)被提升到重載列表的最頂端。

比如,下面的接口會(huì)合并到一起:

interface Document {
    createElement(tagName: any): Element;
}
interface Document {
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
    createElement(tagName: string): HTMLElement;
    createElement(tagName: "canvas"): HTMLCanvasElement;
}

合并后的Document將會(huì)像下面這樣:

interface Document {
    createElement(tagName: "canvas"): HTMLCanvasElement;
    createElement(tagName: "div"): HTMLDivElement;
    createElement(tagName: "span"): HTMLSpanElement;
    createElement(tagName: string): HTMLElement;
    createElement(tagName: any): Element;
}

合并命名空間

與接口相似,同名的命名空間也會(huì)合并其成員。 命名空間會(huì)創(chuàng)建出命名空間和值,我們需要知道這兩者都是怎么合并的。

對(duì)于命名空間的合并,模塊導(dǎo)出的同名接口進(jìn)行合并,構(gòu)成單一命名空間內(nèi)含合并后的接口。

對(duì)于命名空間里值的合并,如果當(dāng)前已經(jīng)存在給定名字的命名空間,那么后來的命名空間的導(dǎo)出成員會(huì)被加到已經(jīng)存在的那個(gè)模塊里。

Animals聲明合并示例:

namespace Animals {
    export class Zebra { }
}

namespace Animals {
    export interface Legged { numberOfLegs: number; }
    export class Dog { }
}

等同于:

namespace Animals {
    export interface Legged { numberOfLegs: number; }

    export class Zebra { }
    export class Dog { }
}

除了這些合并外,你還需要了解非導(dǎo)出成員是如何處理的。 非導(dǎo)出成員僅在其原始存在于的命名空間(未合并的)之內(nèi)可見。這就是說合并之后,從其它命名空間合并進(jìn)來的成員無法訪問非導(dǎo)出成員。

下例提供了更清晰的說明:

namespace Animal {
    let haveMuscles = true;

    export function animalsHaveMuscles() {
        return haveMuscles;
    }
}

namespace Animal {
    export function doAnimalsHaveMuscles() {
        return haveMuscles;  // <-- error, haveMuscles is not visible here
    }
}

因?yàn)?code>haveMuscles并沒有導(dǎo)出,只有animalsHaveMuscles函數(shù)共享了原始未合并的命名空間可以訪問這個(gè)變量。 doAnimalsHaveMuscles函數(shù)雖是合并命名空間的一部分,但是訪問不了未導(dǎo)出的成員。

命名空間與類和函數(shù)和枚舉類型合并

命名空間可以與其它類型的聲明進(jìn)行合并。 只要命名空間的定義符合將要合并類型的定義。合并結(jié)果包含兩者的聲明類型。 Typescript使用這個(gè)功能去實(shí)現(xiàn)一些JavaScript里的設(shè)計(jì)模式。

合并命名空間和類

這讓我們可以表示內(nèi)部類。

class Album {
    label: Album.AlbumLabel;
}
namespace Album {
    export class AlbumLabel { }
}

合并規(guī)則與上面合并命名空間小節(jié)里講的規(guī)則一致,我們必須導(dǎo)出AlbumLabel類,好讓合并的類能訪問。 合并結(jié)果是一個(gè)類并帶有一個(gè)內(nèi)部類。 你也可以使用命名空間為類增加一些靜態(tài)屬性。

除了內(nèi)部類的模式,你在JavaScript里,創(chuàng)建一個(gè)函數(shù)稍后擴(kuò)展它增加一些屬性也是很常見的。 Typescript使用聲明合并來達(dá)到這個(gè)目的并保證類型安全。

function buildLabel(name: string): string {
    return buildLabel.prefix + name + buildLabel.suffix;
}

namespace buildLabel {
    export let suffix = "";
    export let prefix = "Hello, ";
}

alert(buildLabel("Sam Smith"));

相似的,命名空間可以用來擴(kuò)展枚舉型:

enum Color {
    red = 1,
    green = 2,
    blue = 4
}

namespace Color {
    export function mixColor(colorName: string) {
        if (colorName == "yellow") {
            return Color.red + Color.green;
        }
        else if (colorName == "white") {
            return Color.red + Color.green + Color.blue;
        }
        else if (colorName == "magenta") {
            return Color.red + Color.blue;
        }
        else if (colorName == "cyan") {
            return Color.green + Color.blue;
        }
    }
}

非法的合并

TypeScript并非允許所有的合并。 目前,類不能與其它類或變量合并。 想要了解如何模仿類的合并,請參考TypeScript的混入

模塊擴(kuò)展

雖然JavaScript不支持合并,但你可以為導(dǎo)入的對(duì)象打補(bǔ)丁以更新它們。讓我們考察一下這個(gè)玩具性的示例:

// observable.js
export class Observable<T> {
    // ... implementation left as an exercise for the reader ...
}

// map.js
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
    // ... another exercise for the reader
}

它也可以很好地工作在TypeScript中, 但編譯器對(duì) Observable.prototype.map一無所知。 你可以使用擴(kuò)展模塊來將它告訴編譯器:

// observable.ts stays the same
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
    interface Observable<T> {
        map<U>(f: (x: T) => U): Observable<U>;
    }
}
Observable.prototype.map = function (f) {
    // ... another exercise for the reader
}

// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());

模塊名的解析和用import/export解析模塊標(biāo)識(shí)符的方式是一致的。 更多信息請參考 Modules。 當(dāng)這些聲明在擴(kuò)展中合并時(shí),就好像在原始位置被聲明了一樣。但是,你不能在擴(kuò)展中聲明新的頂級(jí)聲明--僅可以擴(kuò)展模塊中已經(jīng)存在的聲明。

全局?jǐn)U展

你也以在模塊內(nèi)部添加聲明到全局作用域中。

// observable.ts
export class Observable<T> {
    // ... still no implementation ...
}

declare global {
    interface Array<T> {
        toObservable(): Observable<T>;
    }
}

Array.prototype.toObservable = function () {
    // ...
}

全局?jǐn)U展與模塊擴(kuò)展的行為和限制是相同的。

上一篇:ASP.NET 4下一篇:聯(lián)合類型