├── README.md ├── 1. Basic Types └── Basic Types.md ├── 3. Classes └── Classes.md ├── 2. Interfaces └── Interfaces.md ├── 4. Modules └── Modules.md └── 5. Functions └── Functions.md /README.md: -------------------------------------------------------------------------------- 1 | ### Typescript Handbook 2 | 3 | 1. Basic Types 4 | * Boolean 5 | * Number 6 | * String 7 | * Array 8 | * Enum 9 | * Any 10 | * Void 11 | 12 | 2. Interfaces 13 | * Our First Interface 14 | * Optional Properties 15 | * Function Types 16 | * Array Types 17 | * Class Types 18 | * Extending Interfaces 19 | * Hybrid Types 20 | 21 | 3. Classes 22 | * Classes 23 | * Inheritance 24 | * Private / Public modifiers 25 | * Accessors 26 | * Static Properties 27 | * Advanced Techniques 28 | 29 | 4. Modules 30 | * Splitting Across Files 31 | * Going External 32 | * Export = 33 | * Alias 34 | * Optional Module Loading and Other Advanced Loading Scenarios 35 | * Working with Other JavaScript Libraries 36 | * Pitfalls of Modules 37 | 38 | 5. Functions 39 | * Functions 40 | * Function Types 41 | * Optional and Default Parameters 42 | * Rest Parameters 43 | * Lambdas and using 'this' 44 | * Overloads 45 | 46 | 6. Generics 47 | * Hello World of Generics 48 | * Working with Generic Type Variables 49 | * Generic Types 50 | * Generic Classes 51 | * Generic Constraints 52 | 53 | 7. Common Errors 54 | * Commonly Confusing Errors 55 | 56 | 8. Mixins 57 | * Mixin sample 58 | * Understanding the sample 59 | 60 | 9. Delcaration Merging 61 | * Basic Concepts 62 | * Merging Interfaces 63 | * Merging Modules 64 | * Merging Modules with Classes, Functions, and Enums 65 | * Disallowed Merges 66 | 67 | 10. Type Inference 68 | * Basics 69 | * Best common type 70 | * Contextual Type 71 | 72 | 11. Type Compatibility 73 | * Starting out 74 | * Comparing two functions 75 | * Enums 76 | * Classes 77 | * Generics 78 | * Advanced Topics 79 | 80 | 12. Writing .d.ts files 81 | * Guidelines and Specifics 82 | * Examples 83 | -------------------------------------------------------------------------------- /1. Basic Types/Basic Types.md: -------------------------------------------------------------------------------- 1 | 基本类型 2 | -- 3 | 4 | 为了让程序更有用,我们需要兼容几种最基本的数据单元:数字,字符串,结构,布尔值等等。在 TypeScript 中,我们支持和 Javascript 几乎一样多的类型,并且新增了实用的枚举类型。 5 | 6 | ### Boolean 布尔值 7 | 最基础的数据类型就是简单的 true/false ,在 Javascript 和 TypeScript (以及其他语言)中被称作是 "boolean"。 8 | 9 | ```ts 10 | var isDone: boolean = false; 11 | ``` 12 | 13 | ### Number 数字 14 | 和 Javascript 一样,在 TypeScript 中所有的数字都是浮点值。这些浮点值的类型为 "number"。 15 | 16 | ```ts 17 | var height: number = 6; 18 | ``` 19 | 20 | ### String 字符串 21 | 22 | 另一个在用 Javascript 编写网页和服务端代码时很基础的部分是处理文本数据。和其他语言一样,我们使用 "string" 类型来表示那些文本数据。和 Javascript 一样,TypeScript 也使用双引号或单引号来围绕字符串数据。 23 | 24 | ```ts 25 | var name: string = "bob"; 26 | name = 'smith'; 27 | ``` 28 | 29 | 30 | ### Array 数组 31 | 32 | TypeScript 和 Javascript 一样,允许你使用数组。数组类型的定义可以有两种写法。第一种写法,你在数组元素类型后面添加‘[]’来表示这是一个该类型的数组。 33 | 34 | ```ts 35 | var list:number[] = [1, 2, 3]; 36 | ``` 37 | 38 | 第二种写法使用一种通用的数组类型表示,Array<数组元素类型> 39 | 40 | ```ts 41 | var list:Array = [1, 2, 3]; 42 | ``` 43 | 44 | ### Enum 枚举 45 | 46 | 枚举是在 Javascript 标准的类型基础上增加的一个有用的类型。类似 C# 等语言,枚举可以给数字集合里的值一些友好的命名。 47 | 48 | ```ts 49 | enum Color {Red, Green, Blue}; 50 | var c: Color = Color.Green; 51 | ``` 52 | 53 | 默认情况下,枚举从 0 开始给它们的成员赋值。你也可以自己设置成员的值。比如,我们从 1 开始给成员赋值: 54 | 55 | ```ts 56 | enum Color {Red = 1, Green, Blue}; 57 | var c: Color = Color.Green; 58 | ``` 59 | 60 | 甚至可以给所有的枚举成员手动赋值: 61 | 62 | ```ts 63 | enum Color {Red = 1, Green = 2, Blue = 4}; 64 | var c: Color = Color.Green; 65 | ``` 66 | 67 | 枚举的一个有用的特征是你可以根据值找到枚举里对应的名称。比如,我们可以通过下面的方式获取 Color 枚举中 值为 2 的颜色名称: 68 | 69 | ```ts 70 | enum Color {Red = 1, Green, Blue}; 71 | var colorName: string = Color[2]; 72 | 73 | alert(colorName); 74 | ``` 75 | 76 | ### Any 任何类型 77 | 78 | 在编写应用程序的时候,我们可能需要描述未知数据变量的类型。这些变量的值来自于动态的内容,比如来自用户或者第三方的库。在这些场景下,我们想要关闭类型检查,并让这些值通过编译检查。在这种情况下,我们给这些变量赋予 "any" 类型: 79 | 80 | ```ts 81 | var notSure: any = 4; 82 | notSure = "maybe a string instead"; 83 | notSure = false; // okay, definitely a boolean 84 | ``` 85 | 86 | "any" 类型是一个兼容已有的 Javascript 代码的强大的解决方案,允许你在编译的时候优雅的开启或关闭类型检查。 87 | 88 | 如果你知道这些变量的部分类型,但也许不是全部,这个时候使用 "any" 类型就很方便。比如,你有一个拥有不同类型元素的数组: 89 | 90 | ```ts 91 | var list:any[] = [1, true, "free"]; 92 | 93 | list[1] = 100; 94 | ``` 95 | 96 | ### Void 空 97 | 98 | 也许在某种角度来看,和任何类型对立的就是 void,没有任何类型。你可以经常在没有返回值的函数的类型里见到。 99 | 100 | ```ts 101 | function warnUser(): void { 102 | alert("This is my warning message"); 103 | } 104 | ``` -------------------------------------------------------------------------------- /3. Classes/Classes.md: -------------------------------------------------------------------------------- 1 | 类 2 | -- 3 | 4 | 传统的 JavaScript 里实现可以重用的组件主要依赖于函数和原型链继承,但是这让那些更习惯使用原生拥有类和继承的面向对象语言的程序员感觉有些棘手。从 ECMAScript 6,下一个版本的 Javascript 开始,JavaScript 程序员将能够使用原生的类来实现面向对象。在 TypeScript 中,我们让开发者无需等待下个版本的 Javascript,现在就开始使用这些技术进行开发,然后把代码编译成兼容所有主流浏览器和平台的 Javascript。 5 | 6 | ### 类 7 | 8 | 让我们先简单看一下基于类的实现范例: 9 | 10 | ```ts 11 | class Greeter { 12 | greeting: string; 13 | constructor(message: string) { 14 | this.greeting = message; 15 | } 16 | greet() { 17 | return "Hello, " + this.greeting; 18 | } 19 | } 20 | 21 | var greeter = new Greeter("world"); 22 | ``` 23 | 24 | 如果你使用过 C# 或者 Java,这种语法看起来应该很熟悉。我们声明了一个 'Greeter' 类。这个类有三个成员,一个叫做 'greeting' 的属性,一个构造函数和一个方法 'greet'。 25 | 26 | 你可能会注意到在类里我们引用类的某个成员的时候,我们用了前缀 'this.'。这表明它是成员访问。 27 | 28 | 最后一行,我们使用 'new' 来构造了 Greeter 类的一个实例。这会调用我们之前定义的构造函数,根据 Greeter 的特征创建一个对象,然后运行构造函数来初始化它。 29 | 30 | ### 继承 31 | 32 | 在 TypeScript 中,我们可以使用普通的面向对象的模式。当然,在基于类的编程中其中一个最基本的模式就是可以继承已有的类来创建新的类。 33 | 34 | 让我们看个例子: 35 | 36 | ```ts 37 | class Animal { 38 | name:string; 39 | constructor(theName: string) { this.name = theName; } 40 | move(meters: number) { 41 | alert(this.name + " moved " + meters + "m."); 42 | } 43 | } 44 | 45 | class Snake extends Animal { 46 | constructor(name: string) { super(name); } 47 | move() { 48 | alert("Slithering..."); 49 | super.move(5); 50 | }˝ 51 | } 52 | 53 | class Horse extends Animal { 54 | constructor(name: string) { super(name); } 55 | move() { 56 | alert("Galloping..."); 57 | super.move(45); 58 | } 59 | } 60 | 61 | var sam = new Snake("Sammy the Python"); 62 | var tom: Animal = new Horse("Tommy the Palomino"); 63 | 64 | sam.move(); 65 | tom.move(34); 66 | ``` 67 | 68 | 这个例子涵盖了 TypeScript 中很大一部分继承的特性,这些特性和其他一些语言很类似。我们看到使用 'extends' 这个关键词来创建一个子类。你可以看到 'Horse' 和 'Snake' 继承了基类,并且获得了基类的特征。 69 | 70 | 这个例子也说明了可以在子类中重写基类已有的方法。这里 'Snake' 和 'Horse' 都创建了一个 'move' 方法,覆盖了从 'Animal' 继承过来的 'move',给予子类不同的特征。 71 | 72 | ### Private/Public modifiers 私有/公有 修饰符 73 | 74 | #### Public by default 默认 Public 75 | 76 | 你可能已经注意到上面的例子里我们不是一定需要使用 'public' 让类的成员对外可见。像 C# 这样的语言要求每个成员必须显式的标记 'public' 以表示对外可见。在 TypeScript 中,成员默认就是 public 的。 77 | 78 | 你也可以将成员标记为 private,总之你可以控制类的哪些成员对外可见。我们可以重写下前面实现的 'Animal' 类: 79 | 80 | ```ts 81 | class Animal { 82 | private name:string; 83 | constructor(theName: string) { this.name = theName; } 84 | move(meters: number) { 85 | alert(this.name + " moved " + meters + "m."); 86 | } 87 | } 88 | ``` 89 | 90 | #### Understanding private 理解 private 91 | 92 | TypeScript 是一个结构性的类型系统 ([Structural type system](http://en.wikipedia.org/wiki/Structural_type_system))。当我们比较两个类型的时候,不去理会他们从哪里来,如果这两个类型的每个成员的类型都是兼容的,那就认为这两个类型是兼容的。 93 | 94 | 当比较两个拥有私有成员的类型的时候,我们处理方式有些不同。如果两个类型认为是互相兼容的,并且他们中的一个有私有成员,那么另一个必须拥有一个来自相同声明的私有成员。 95 | 96 | 让我们看个例子来更好的理解下这是怎么回事: 97 | 98 | ```ts 99 | class Animal { 100 | private name:string; 101 | constructor(theName: string) { this.name = theName; } 102 | } 103 | 104 | class Rhino extends Animal { 105 | constructor() { super("Rhino"); } 106 | } 107 | 108 | class Employee { 109 | private name:string; 110 | constructor(theName: string) { this.name = theName; } 111 | } 112 | 113 | var animal = new Animal("Goat"); 114 | var rhino = new Rhino(); 115 | var employee = new Employee("Bob"); 116 | 117 | animal = rhino; 118 | animal = employee; //error: Animal and Employee are not compatible 119 | ``` 120 | 121 | 在这个例子中,有 'Animal' 和 'Rhino' 两个类,其中 'Rhino' 是 'Animal' 的子类。同时,还有一个 'Employee' 类的特征看上去和 'Animal' 等同。我们创建了这些类的实例,并且尝试把其中的一个赋值给另一个看看会发生什么。因为 'Animal' 和 'Rhino' 共用在 'Animal' 里的私有成员声明 'private name: string' ,因此他们是兼容的。但是,'Employee' 则不同。当我们尝试把 'Employee' 的实例赋值给 'Animal' 时,会提示错误说类型不兼容。虽然 'Employee' 也有一个私有变量叫做 name,但是它和 'Animal' 里的 name 并不相同。 122 | 123 | #### Parameter properties 参数属性 124 | 125 | 支持通过添加参数属性(关键词 'public' 和 'private' )来快捷的创建和初始化类里的成员。这些属性可以让你一步完成类成员的创建和初始化。这里有一个之前示例修改后的版本。注意我们完全抛弃了 'theName', 只是在构造函数的参数上使用缩短的 'private name: string' 来创建和初始化 'name' 成员。 126 | 127 | ```ts 128 | class Animal { 129 | constructor(private name: string) { } 130 | move(meters: number) { 131 | alert(this.name + " moved " + meters + "m."); 132 | } 133 | } 134 | ``` 135 | 136 | 使用 'private' 可以创建和初始化私有成员,类似的使用 'public' 可以创建和初始化公有成员。 137 | 138 | ### Accessors 存取器 139 | 140 | TypeScript 支持使用 getters/setters 来拦截对成员变量的访问。这让你更小粒度的去控制对象上的每个成员如何被访问。 141 | 142 | 让我们把一个简单的类转化成使用 'get' 和 'set'。首先,让我们看一个没有使用 getters 和 setters 的例子。 143 | 144 | ```ts 145 | class Employee { 146 | fullName: string; 147 | } 148 | 149 | var employee = new Employee(); 150 | employee.fullName = "Bob Smith"; 151 | if (employee.fullName) { 152 | alert(employee.fullName); 153 | } 154 | ``` 155 | 156 | 虽然允许直接修改 fullName 看起来很方便,但是我们可以心血来潮的修改名字可能会带来麻烦。 157 | 158 | 在这个版本中,在允许修改 employee 之前,我们检查了用户是否有正确的密码。我们把可以直接修改 fullName 替换成了使用 set 方法来检查密码。同时,我们增加了一个匹配的 get 让前面的示例可以继续无缝的工作。 159 | 160 | ```ts 161 | var passcode = "secret passcode"; 162 | 163 | class Employee { 164 | private _fullName: string; 165 | 166 | get fullName(): string { 167 | return this._fullName; 168 | } 169 | 170 | set fullName(newName: string) { 171 | if (passcode && passcode == "secret passcode") { 172 | this._fullName = newName; 173 | } 174 | else { 175 | alert("Error: Unauthorized update of employee!"); 176 | } 177 | } 178 | } 179 | 180 | var employee = new Employee(); 181 | employee.fullName = "Bob Smith"; 182 | if (employee.fullName) { 183 | alert(employee.fullName); 184 | } 185 | ``` 186 | 187 | 为了证明我们的存取器现在在检查密码,我们可以修改密码来确认当密码不匹配的时候我们会得到一个没有更新权限的提示框。 188 | 189 | 注意:访问器需要你将编译器的输出配置为 ECMAScript 5。 190 | 191 | ### Static Properties 静态属性 192 | 193 | 说到这里,我们只是谈及了类的实例成员,它们只有在类初始化之后才出现。我们也可以创建类的静态成员,它们是在类本身上可见,而不是在实例上可见。在这个例子中,我们将 origin 声明为 static,因为它对于所有的网格都是一个相同的值。每个实例都可以通过类的名称来访问这个值。类似于在实例成员前面增加 'this.',在这里,我们在静态属性的名称前增加 'Grid.'' 前缀。 194 | 195 | ```ts 196 | class Grid { 197 | static origin = {x: 0, y: 0}; 198 | calculateDistanceFromOrigin(point: {x: number; y: number;}) { 199 | var xDist = (point.x - Grid.origin.x); 200 | var yDist = (point.y - Grid.origin.y); 201 | return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale; 202 | } 203 | constructor (public scale: number) { } 204 | } 205 | 206 | var grid1 = new Grid(1.0); // 1x scale 207 | var grid2 = new Grid(5.0); // 5x scale 208 | 209 | alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); 210 | alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10})); 211 | ``` 212 | -------------------------------------------------------------------------------- /2. Interfaces/Interfaces.md: -------------------------------------------------------------------------------- 1 | # 接口 2 | 3 | Typescript 的一个核心原则是做类型检查时只对值的 shape 进行检查。这种原则也被叫做 [duck typing](http://zh.wikipedia.org/zh-cn/%E9%B8%AD%E5%AD%90%E7%B1%BB%E5%9E%8B) 或者 [structural subtyping](http://zh.wikipedia.org/wiki/%E5%AD%90%E7%B1%BB%E5%9E%8B)。在 Typescript 中,接口被用于描述这种类型,也用于在你的代码中以及与外部代码之间定义约定。 4 | 5 | ### 我们的第一个接口 6 | 7 | 请看一个简单的例子: 8 | 9 | ```ts 10 | function printLabel(labelledObj: {label: string}) { 11 | console.log(labelledObj.label); 12 | } 13 | 14 | var myObj = {size: 10, label: "Size 10 Object"}; 15 | printLabel(myObj); 16 | ``` 17 | 18 | 类型检查器会检查对 printLabel 的调用。printLabel 方法接收一个对象作为参数,并要求这个对象带有一个 string 类型的属性 label。请注意除了 label 外,我们传给 printLabel 方法的参数 myObj 含有更多的属性,但是编译器只会要求 label 属性存在并且是正确的类型。 19 | 20 | 下面我们使用接口来完成这个要求: 21 | 22 | ```ts 23 | interface LabelledValue { 24 | label: string; 25 | } 26 | 27 | function printLabel(labelledObj: LabelledValue) { 28 | console.log(labelledObj.label); 29 | } 30 | 31 | var myObj = {size: 10, label: "Size 10 Object"}; 32 | printLabel(myObj); 33 | ``` 34 | 35 | 接口 LabelledValue 用于完成前面提到的要求。我们并不像其他语言那样需要printLabel的参数实现这个接口,只需要这个参数符合限定的 shape。 36 | 37 | 同样需要指出的是类型检查器不会对属性出现的顺序继续宁限制,只对属性以及属性的类型有要求。 38 | 39 | ### 可选属性 40 | 41 | 可选属性在类似 option bags 的模式中很流行,在这类模式中,用户只需要传入一个带有部分默认属性的对象。 42 | 43 | 请看下面的例子: 44 | 45 | ```ts 46 | interface SquareConfig { 47 | color?: string; 48 | width?: number; 49 | } 50 | 51 | function createSquare(config: SquareConfig): {color: string; area: number} { 52 | var newSquare = {color: "white", area: 100}; 53 | if (config.color) { 54 | newSquare.color = config.color; 55 | } 56 | if (config.width) { 57 | newSquare.area = config.width * config.width; 58 | } 59 | return newSquare; 60 | } 61 | 62 | var mySquare = createSquare({color: "black"}); 63 | ``` 64 | 65 | 你可以用一个问号来表示当前属性为可选属性。可选属性的好处是通过描述所有可能存在的属性,来捕获哪些不应该存在的属性。例如我们错误输入了一个传给createSquare方法的参数属性,我们就会得到一个错误提示: 66 | 67 | ```ts 68 | interface SquareConfig { 69 | color?: string; 70 | width?: number; 71 | } 72 | 73 | function createSquare(config: SquareConfig): {color: string; area: number} { 74 | var newSquare = {color: "white", area: 100}; 75 | if (config.color) { 76 | newSquare.color = config.collor; // Type-checker can catch the mistyped name here 77 | } 78 | if (config.width) { 79 | newSquare.area = config.width * config.width; 80 | } 81 | return newSquare; 82 | } 83 | 84 | var mySquare = createSquare({color: "black"}); 85 | ``` 86 | 87 | ### 函数类型 88 | 89 | 接口可以用于描述各种 shape 的 Javascript 对象。除了带有属性的对象之外,接口同样可以用于描述函数类型。 90 | 91 | 函数类型的声明包含对函数的参数列表和返回值的描述。 92 | 93 | ```ts 94 | interface SearchFunc { 95 | (source: string, subString: string): boolean; 96 | } 97 | ``` 98 | 99 | 函数类型接口的使用和其他接口一样,下面是一个对函数类型变量赋值的例子。 100 | 101 | ```ts 102 | var mySearch: SearchFunc; 103 | mySearch = function(source: string, subString: string) { 104 | var result = source.search(subString); 105 | if (result == -1) { 106 | return false; 107 | } 108 | else { 109 | return true; 110 | } 111 | } 112 | ``` 113 | 114 | 函数类型接口并不要求参数的名称与接口定义一致,例如将上面的例子写为这样也是可以的: 115 | 116 | ```ts 117 | var mySearch: SearchFunc; 118 | mySearch = function(src: string, sub: string) { 119 | var result = src.search(sub); 120 | if (result == -1) { 121 | return false; 122 | } 123 | else { 124 | return true; 125 | } 126 | } 127 | ``` 128 | 129 | 函数参数的类型需要与接口中定义的完全一致,函数的返回值类型也是一样。如果上面的方法返回了一个数字或者字符串,类型检查器会警告返回值类型与接口中定义的不一致。 130 | 131 | ### 数组类型 132 | 133 | 与函数类型接口类似,我们也可以定义数组类型接口。数组类型接口包含对索引类型和返回值类型的定义。 134 | 135 | ```ts 136 | interface StringArray { 137 | [index: number]: string; 138 | } 139 | 140 | var myArray: StringArray; 141 | myArray = ["Bob", "Fred"]; 142 | ``` 143 | 144 | 我们支持的索引类型有2种:字符串和数字。在一个接口中同时支持这两种索引是允许的,但是有一个限制:数字索引返回值的类型必须为字符串索引返回值类型的子类。(译者注:通过实验,数字索引返回值的类型必须符合字符串索引返回值类型的 shape,比如下面这样也是可以的:) 145 | 146 | ```ts 147 | interface A { 148 | value: number; 149 | } 150 | 151 | interface B { 152 | value: number; 153 | type: string; 154 | } 155 | 156 | interface a { 157 | [index: number]: B; 158 | [index: string]: A; 159 | } 160 | ``` 161 | 162 | 索引类型接口很适合于描述数字或者 dictionary 模式,但它要求接口中定义的其他属性必须与索引返回值的类型一致。例如,这个例子中的 length 属性不符合索引返回值的类型,类型检查器就会报错: 163 | 164 | ```ts 165 | interface Dictionary { 166 | [index: string]: string; 167 | length: number; // error, the type of 'length' is not a subtype of the indexer 168 | } 169 | ``` 170 | 171 | ### Class 类型 172 | 173 | #### 实现一个接口 174 | 175 | 在 C# 或者 Java 等语言中,接口最常见的用法是强制类实现某种约定,在 Typescript 也可以这样做。 176 | 177 | ```ts 178 | interface ClockInterface { 179 | currentTime: Date; 180 | } 181 | 182 | class Clock implements ClockInterface { 183 | currentTime: Date; 184 | constructor(h: number, m: number) { } 185 | } 186 | ``` 187 | 188 | 你同样可以在接口中定义一个方法并在类中实现,例如下面例子中的 setTime 方法: 189 | 190 | ```ts 191 | interface ClockInterface { 192 | currentTime: Date; 193 | setTime(d: Date); 194 | } 195 | 196 | class Clock implements ClockInterface { 197 | currentTime: Date; 198 | setTime(d: Date) { 199 | this.currentTime = d; 200 | } 201 | constructor(h: number, m: number) { } 202 | } 203 | ``` 204 | 205 | 接口只能用于描述了类的公开部分,不包括私有部分,所以你不能使用接口来强制一个类的私有部分也包含特定的类型。 206 | 207 | #### 类的静态部分和实例部分的区别(Difference between static/instance side of class) 208 | 209 | 当同时使用类和接口时,你最好能记住类的成员有两种类型:静态成员类型和实例成员类型。如果你创建了一个带有构造函数的接口,并尝试创建一个类来实现这个接口,就会发生一个错误: 210 | 211 | ```ts 212 | interface ClockInterface { 213 | new (hour: number, minute: number); 214 | } 215 | 216 | class Clock implements ClockInterface { 217 | currentTime: Date; 218 | constructor(h: number, m: number) { } 219 | } 220 | ``` 221 | 222 | 这是因为当类实现一个接口时,只有类的实例成员会被检查。而构造函数作为类的静态成员,是不会进行检查的。 223 | 224 | 所以,你需要通过某种方式与类的静态成员部分直接进行交流。请看下面的例子: 225 | 226 | ```ts 227 | interface ClockStatic { 228 | new (hour: number, minute: number); 229 | } 230 | 231 | class Clock { 232 | currentTime: Date; 233 | constructor(h: number, m: number) { } 234 | } 235 | 236 | var cs: ClockStatic = Clock; 237 | var newClock = new cs(7, 30); 238 | ``` 239 | 240 | ### 接口继承 241 | 242 | 和类一样,接口之间也可以继承。通过继承可以避免在接口之间拷贝接口成员,也便于将接口拆分为不同用途的元件。 243 | 244 | ```ts 245 | interface Shape { 246 | color: string; 247 | } 248 | 249 | interface Square extends Shape { 250 | sideLength: number; 251 | } 252 | 253 | var square: Square; 254 | square.color = "blue"; 255 | square.sideLength = 10; 256 | ``` 257 | 258 | 一个接口可以同时继承多个其他接口,从而创建一个包含其他所有接口的合集。 259 | 260 | ```ts 261 | interface Shape { 262 | color: string; 263 | } 264 | 265 | interface PenStroke { 266 | penWidth: number; 267 | } 268 | 269 | interface Square extends Shape, PenStroke { 270 | sideLength: number; 271 | } 272 | 273 | var square: Square; 274 | square.color = "blue"; 275 | square.sideLength = 10; 276 | square.penWidth = 5.0; 277 | ``` 278 | 279 | ### 混合类型 280 | 281 | 接口可以用于描述 Javascript 中各种丰富的类型。由于 Javascript 动态和灵活的特点,你时常会遇到一个包含了以上介绍的各种类型的对象。 282 | 283 | 例如下面这个对象,它即是一个方法,也是一个对象,同时还带有额外的属性: 284 | 285 | ```ts 286 | interface Counter { 287 | (start: number): string; 288 | interval: number; 289 | reset(): void; 290 | } 291 | 292 | var c: Counter; 293 | c(10); 294 | c.reset(); 295 | c.interval = 5.0; 296 | ``` 297 | 298 | 当需要与第三方 Javascript 代码相互调用时,你时常需要用到混合类型接口来完整的描述一个类型。 299 | -------------------------------------------------------------------------------- /4. Modules/Modules.md: -------------------------------------------------------------------------------- 1 | # 模块 2 | 3 | 本章会列举在 TypeScript 中使用模块的各种方法。我们会讲到内部和外部模块,并讨论何时应该使用以及如何使用。我们也会讲到如何使用外部模块等高级话题,以及如何处理在 TypeScript 中使用模块时会遇到的一些常见问题。 4 | 5 | #### 第一步 6 | 7 | 让我们从一段将会贯穿于本章内容的示例程序开始。在这个程序中,我们编写了几个简单的 string 校验器,用于检查例如用户在网页上的输入或者是从外部获取的数据文件。 8 | 9 | ##### 写在一个文件中的校验器 10 | 11 | ```ts 12 | interface StringValidator { 13 | isAcceptable(s: string): boolean; 14 | } 15 | 16 | var lettersRegexp = /^[A-Za-z]+$/; 17 | var numberRegexp = /^[0-9]+$/; 18 | 19 | class LettersOnlyValidator implements StringValidator { 20 | isAcceptable(s: string) { 21 | return lettersRegexp.test(s); 22 | } 23 | } 24 | 25 | class ZipCodeValidator implements StringValidator { 26 | isAcceptable(s: string) { 27 | return s.length === 5 && numberRegexp.test(s); 28 | } 29 | } 30 | 31 | // Some samples to try 32 | var strings = ['Hello', '98052', '101']; 33 | // Validators to use 34 | var validators: { [s: string]: StringValidator; } = {}; 35 | validators['ZIP code'] = new ZipCodeValidator(); 36 | validators['Letters only'] = new LettersOnlyValidator(); 37 | // Show whether each string passed each validator 38 | strings.forEach(s => { 39 | for (var name in validators) { 40 | console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name); 41 | } 42 | }); 43 | ``` 44 | 45 | #### 加入模块化 46 | 47 | 当我们加入更多的校验器时,我们需要有一种组织代码的方法来避免对象之间的命名冲突。让我们通过把对象放进模块来避免把太多名字放入全局命名空间。 48 | 49 | 在下面的例子中,我们将所有校验器相关的类型放入一个叫做 Validation 的模块中。对于需要对外界可见的接口和类,我们使用 export 来注明。相反,变量 lettersRegexp 和 numberRegexp 是实现的细节,所以它们不需要被注明为 export,从而对外界是不可见的。在文件最后的测试代码中,我们需要从模块外调用模块内部的类型,可以使用模块名称来修饰类型,比如:Validation.LettersOnlyValidator。 50 | 51 | ##### 模块化的校验器 52 | 53 | ```ts 54 | module Validation { 55 | export interface StringValidator { 56 | isAcceptable(s: string): boolean; 57 | } 58 | 59 | var lettersRegexp = /^[A-Za-z]+$/; 60 | var numberRegexp = /^[0-9]+$/; 61 | 62 | export class LettersOnlyValidator implements StringValidator { 63 | isAcceptable(s: string) { 64 | return lettersRegexp.test(s); 65 | } 66 | } 67 | 68 | export class ZipCodeValidator implements StringValidator { 69 | isAcceptable(s: string) { 70 | return s.length === 5 && numberRegexp.test(s); 71 | } 72 | } 73 | } 74 | 75 | // Some samples to try 76 | var strings = ['Hello', '98052', '101']; 77 | // Validators to use 78 | var validators: { [s: string]: Validation.StringValidator; } = {}; 79 | validators['ZIP code'] = new Validation.ZipCodeValidator(); 80 | validators['Letters only'] = new Validation.LettersOnlyValidator(); 81 | // Show whether each string passed each validator 82 | strings.forEach(s => { 83 | for (var name in validators) { 84 | console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name); 85 | } 86 | }); 87 | ``` 88 | 89 | ### 划分为多个文件 90 | 91 | 当程序规模增长时,我们需要把代码划分到多个模块中以便于维护。 92 | 93 | 下面我们会把 Validation 模块划分到多个文件中。虽然分为了多个文件,但是他们仍然可以属于统一个模块,并且作为同一个模块被使用。由于这些文件之间存在依赖,我们加上了 reference 标签来告诉编译器这些文件之间的联系。我们的测试代码没有变化。 94 | 95 | #### 多文件的内部模块 96 | 97 | ##### Validation.ts 98 | 99 | ```ts 100 | module Validation { 101 | export interface StringValidator { 102 | isAcceptable(s: string): boolean; 103 | } 104 | } 105 | ``` 106 | 107 | ##### LettersOnlyValidator.ts 108 | 109 | ```ts 110 | /// 111 | module Validation { 112 | var lettersRegexp = /^[A-Za-z]+$/; 113 | export class LettersOnlyValidator implements StringValidator { 114 | isAcceptable(s: string) { 115 | return lettersRegexp.test(s); 116 | } 117 | } 118 | } 119 | ``` 120 | 121 | ##### ZipCodeValidator.ts 122 | 123 | ```ts 124 | /// 125 | module Validation { 126 | var numberRegexp = /^[0-9]+$/; 127 | export class ZipCodeValidator implements StringValidator { 128 | isAcceptable(s: string) { 129 | return s.length === 5 && numberRegexp.test(s); 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | ##### Test.ts 136 | 137 | ```ts 138 | /// 139 | /// 140 | /// 141 | 142 | // Some samples to try 143 | var strings = ['Hello', '98052', '101']; 144 | // Validators to use 145 | var validators: { [s: string]: Validation.StringValidator; } = {}; 146 | validators['ZIP code'] = new Validation.ZipCodeValidator(); 147 | validators['Letters only'] = new Validation.LettersOnlyValidator(); 148 | // Show whether each string passed each validator 149 | strings.forEach(s => { 150 | for (var name in validators) { 151 | console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name); 152 | } 153 | }); 154 | ``` 155 | 156 | 当有多个文件时,我们需要确定所有编译后的文件都得到加载,有两种方法: 157 | 158 | 第一种方法,我们可以使用 --out 标志来将所有输入的文件合并输出为一个 JavaScript 文件: 159 | 160 | ```sh 161 | tsc --out sample.js Test.ts 162 | ``` 163 | 164 | 编译器会根据文件中的 reference 标签自动安排各个输入文件在输出文件中的次序。你同样也可以依次写明所有的输入文件: 165 | 166 | ```sh 167 | tsc --out sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts 168 | ``` 169 | 170 | 在另一种方法中,我们可以将每一个输入文件单独编译,然后在网页上使用 <script> 标签按顺序加载每一个编译后的文件,例如: 171 | 172 | ##### MyTestPage.html(摘录) 173 | 174 | ```html 175 |