TypeScript手冊翻譯系列8-聲明合併

聲明合併(Declaration Merging)

TypeScript中一些獨到的概念(unique concepts)來自於須要描述JavaScript對象的shape在type級發生了什麼。其中一個例子就是聲明合併('declaration merging')。理解這個概念會對TypeScript中使用現有的JavaScript帶來優點,也會打開更高級的抽象概念之門。

在深刻聲明如何合併以前,首先描述下什麼是聲明合併。

聲明合併是指編譯器將有相同名稱的兩個不一樣聲明合併到一個定義中,合併後的定義包含兩個聲明的全部特性。聲明合併並不僅限於兩個聲明,任意數量的聲明均可以合併。

typescript

基本概念(Basic Concepts)

在TypeScript中,有三個組都存在聲明:namespace/module, type, 或value。建立namespace/module的聲明是在寫type時用點標記法來訪問的。建立type的聲明在聲明shape並綁定到一個給定名稱時是可見的。最後,建立value的聲明是在output JavaScript((例如functions和variables))中可見的編程

聲明類型 Namespace Type      Valuecanvas

Module    X                      X編程語言

Class                 X      X函數

Interface                 X ui

Function                     Xspa

Variable                     X.net

理解每一類聲明建立了什麼,將有助於理解當執行聲明合併時什麼被合併。
翻譯

合併接口(Merging Interfaces)

最簡單,多是最多見的聲明合併類型就是接口合併。這種合併就是將兩個聲明的成員機械地合併到一個接口中,成員名稱保持相同。
code

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

interface Box {
   scale: number;
}

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


接口中非函數成員必須惟一。若是兩個接口都聲明瞭有相同名字的非函數成員,編譯器將報錯。

對於函數成員,同名的每一個函數成員被視爲描述相同函數的一個重載(overload)。值得注意的是:前面一個interface A與後一個interface A (稱爲A')合併時,A'的重載將比interface A有更高的優先級。 

下面是一個例子:

interface Document {
   createElement(tagName: any): Element;
}

interface Document {
   createElement(tagName: string): HTMLElement;
}

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


兩個interfaces將合併建立出一個聲明。注意每一個組的元素保持相同順序,這些組合並後,後面重載集將靠前:

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

合併模塊(Merging Modules)

相似於接口,同名的模塊也合併成員。由於模塊會建立namespace與value,咱們須要理解這兩種是如何合併的。

爲了合併namespaces,在每一個模塊中聲明的exported接口類型定義被合併, 造成一個namespace,其中有合併後的接口定義。
爲了合併value,在每一個聲明site,若是存在同名的module,就擴展先前的module,將第二個module的exported成員添加到第一個module中。

在這個例子中,合併'Animals'聲明

module Animals {    
   export class Zebra { }
}

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


等同於:

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


上面這個模塊合併模型是一個很是好的起點,但爲了更完整地理解,就須要理解非exported成員發生了什麼。exported成員只能在原有的(未合併)模塊中可見。這意味着在合併後,從其餘聲明來的合併成員不能看到這些exported成員。

在這個例子中能夠看得更爲清楚:

module Animal {    
   var haveMuscles = true;
   
   export function animalsHaveMuscles() {        
       return haveMuscles;
   }
}

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


由於haveMuscles沒有exported,只有在未合併的module中的animalsHaveMuscles函數才能看到這個符號。doAnimalsHaveMuscles函數雖然是合併後的Animal模塊的一部分,但它不能看到這個非exported成員。

合併模塊與類、函數以及枚舉(Merging Modules with Classes, Functions, and Enums)

模塊很是靈活,還能夠與其餘類型的聲明作合併。爲此,模塊聲明必須放在它將合併的聲明後面。最終的聲明包含兩種聲明類型的全部屬性。TypeScript利用這種能力來建模JavaScript以及其餘編程語言中的一些patterns。

咱們討論的第一個模塊合併是合併模塊與類,這樣就能夠描述內部類(inner class)。

class Album {
   label: Album.AlbumLabel;
}

module Album {    
   export class AlbumLabel { }
}


合併後成員的可見性規則與"合併模塊"章節中的規則相同,因此咱們必須export AlbumLabel類,以便合併後的類可看到它。最終結果是一個類在另外一個類中被管理。也能夠用modules將更多的靜態成員添加到一個類中。

除了內部類(inner class)這個模式之外,你可能也熟悉JavaScript中建立函數,而後向這個函數添加屬性來擴展它。TypeScript使用聲明合併來構建相似的定義,可是以type-safe方式來構建。 

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

module buildLabel {    
   export var suffix = "";    
   export var prefix = "Hello, ";
}

alert(buildLabel("Sam Smith"));


相似地,modules能夠用靜態成員來擴展枚舉:

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

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

不容許的合併(Disallowed Merges)

TypeScript中並不是全部的合併都容許。如今,classes不能與其餘classes合併,variables與classes不能合併,interfaces與classes不能合併。 關於模仿classes合併信息可參見Mixins in TypeScript 章節。

參考資料

[1] http://www.typescriptlang.org/Handbook#declaration-merging

[2] TypeScript系列1-簡介及版本新特性, http://my.oschina.net/1pei/blog/493012

[3] TypeScript手冊翻譯系列1-基礎類型, http://my.oschina.net/1pei/blog/493181

[4] TypeScript手冊翻譯系列2-接口, http://my.oschina.net/1pei/blog/493388

[5] TypeScript手冊翻譯系列3-類, http://my.oschina.net/1pei/blog/493539

[6] TypeScript手冊翻譯系列4-模塊, http://my.oschina.net/1pei/blog/495948

[7] TypeScript手冊翻譯系列5-函數, http://my.oschina.net/1pei/blog/501273

[8] TypeScript手冊翻譯系列6-泛型, http://my.oschina.net/1pei/blog/513483

[9] TypeScript手冊翻譯系列7-常見錯誤與mixins, http://my.oschina.net/1pei/blog/513499

相關文章
相關標籤/搜索