TypeScript手册翻译系列8-声明合并

TypeScript 中声明合并详解与应用
本文深入探讨了 TypeScript 中的声明合并概念,包括基本原理、接口合并、模块合并、类与函数合并以及不可合并的情况,并通过实例展示了声明合并的实际应用。

声明合并(Declaration Merging)

TypeScript中一些独到的概念(unique concepts)来自于需要描述JavaScript对象的shape在type级发生了什么。其中一个例子就是声明合并('declaration merging')。理解这个概念会对TypeScript中使用现有的JavaScript带来优势,也会打开更高级的抽象概念之门。

在深入声明如何合并之前,首先描述下什么是声明合并。

声明合并是指编译器将有相同名称的两个不同声明合并到一个定义中,合并后的定义包含两个声明的所有特性。声明合并并不只限于两个声明,任意数量的声明都可以合并。

基本概念(Basic Concepts)

在TypeScript中,有三个组都存在声明:namespace/module, type, 或value。创建namespace/module的声明是在写type时用点标记法来访问的。创建type的声明在声明shape并绑定到一个给定名称时是可见的。最后,创建value的声明是在output JavaScript((例如functions和variables))中可见的

声明类型 Namespace Type      Value

Module    X                      X

Class                 X      X

Interface                 X

Function                     X

Variable                     X

理解每一类声明创建了什么,将有助于理解当执行声明合并时什么被合并。

合并接口(Merging Interfaces)

最简单,可能是最常见的声明合并类型就是接口合并。这种合并就是将两个声明的成员机械地合并到一个接口中,成员名称保持相同。

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


转载于:https://my.oschina.net/1pei/blog/513649

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值