Interfaces
作为TypeScript中的核心特色之一,能够让类型检查帮助我们知道一个对象应该有什么,相比我们在编写JavaScript的时候经常遇到函数需要传递参数,可能在编写的时候知道这个对象能够提供哪些值,但是以后维护的时候负责看这段代码的人都无法确认这个对象还有其他的哪些值,就需要翻阅源码看看调用这个函数的代码。
第一个接口
在开始正题之前我们先来一个简单的例子。
--TypeScript
1 function printLabel(labelObject: { label: string }) { 2 console.log(labelObject.label); 3 } 4 5 var myobj = { size: 10, label: "Size 10 Object" }; 6 printLabel(myobj);
--JavaScript
function printLabel(labelObject) { console.log(labelObject.label); } var myobj = { size: 10, label: "Size 10 Object" }; printLabel(myobj);
类型检查会在我们调用printLabel的时候检查传进去的参数,确保该参数中存在一个名字为label并且类型为string的属性,当然我们可以看到这个参数的值远远比我们函数要求的多,但这并不会造成影响,通过最终的源码我们也可以看到最终生成的js代码并没有比原来多出多少,但是却能够为我们提供强大的类型检查。
下面我们可以引入正题了,我们将使用接口的方式实现上面的例子。
--TypeScript
1 interface LabelledValue { 2 label: string 3 } 4 5 function printLabel(labelObject: LabelledValue) { 6 console.log(labelObject.label); 7 } 8 9 var myobj = { size: 10, label: "Size 10 Object" }; 10 printLabel(myobj);
--JavaScript
同上
这里我们看到如果利用接口能够更便于管理,更主要的是接口中除了可以定义属性也可以定义函数等,对于中大型项目,特别是前后端分离的网站来说对于以后的维护和迭代能顾节省时间成本并提高质量。
可选属性
但是由于历史原因,TypeScript并不能孤立存在,还是需要兼容其他库,那么就导致我们的接口还要考虑另一种情况就是可选值,比如下面这个例子。
--TypeScript
1 interface SquareConfig { 2 color?: string; 3 width?: number; 4 } 5 6 function createSquare(config: SquareConfig): { color: string; area: number } { 7 var newsquare = { color: "white", area: 100 }; 8 if (config.color) { 9 newsquare.color = config.color; 10 } 11 if (config.width) { 12 newsquare.area = config.width * config.width; 13 } 14 return newsquare; 15 } 16 17 var mySquare = createSquare({ color: "black" });
--JavaScript
1 function createSquare(config) { 2 var newsquare = { color: "white", area: 100 }; 3 if (config.color) { 4 newsquare.color = config.color; 5 } 6 if (config.width) { 7 newsquare.area = config.width * config.width; 8 } 9 return newsquare; 10 } 11 12 var mySquare = createSquare({ color: "black" });
通过接口我们知道可选属性就是在属性名称的后面加上问号就可以了,但是开发的时候要注意就是要通过if判断下该值是否存在。
函数类型
玩转了属性,下面我们开始在接口中放入函数,下面我们先放一个函数。
--TypeScript
1 interface SearchFunc { 2 (source: string, substring: string): boolean; 3 } 4 5 var mySearch: SearchFunc; 6 mySearch = function (source: string, substring: string) { 7 var result = source.search(substring); 8 if (result == -1) { 9 return false; 10 } else { 11 return true; 12 } 13 };
--JavaScript
var mySearch; mySearch = function (source, substring) { var result = source.search(substring); if (result == -1) { return false; } else { return true; } };
大家肯定会很奇怪,下面为什么定义了这个接口的变量但是赋的确是一个函数,如果大家有C#和java语言的基础会发现SearchFunc中的函数是没有函数名的,所以mySearch的类型就是一个函数,只是会进行类型检查,你是不能赋其他函数签名不一样的函数给他的。
数组类型
接口除了可以描述函数类型也可以描述数组类型,数组类型拥有一个“index”类型,是用来索引数组的,利用这个我们就可以实现除了数组以外还能够实现字典类型。
--TypeScript
1 interface StringArray { 2 [index: number]: string; 3 } 4 5 var myArray: StringArray; 6 myArray = ["Bob", "Fred"];
--JavaScript
1 var myArray; 2 myArray = ["Bob", "Fred"];
Index类型能够支持两种类型:string和number,所以我们能够实现字典类型,比如下面这种类型。
--TypeScript
1 interface StringArray { 2 [index: string]: string; 3 } 4 5 var myArray: StringArray; 6 myArray = { 7 "dede": "dede", 8 "ete":"dede" 9 };
--JavaScript
1 var myArray; 2 myArray = { 3 "dede": "dede", 4 "ete": "dede" 5 };
类类型
像C#和Java语言中一样,接口最基础的作用就是让类去实现接口,所以这也是TypeScript语言的特点之一。比如下面的例子我们将实现一个带有一个属性的接口。
--TypeScript
1 interface ClockInterface { 2 currentTime: Date 3 } 4 5 class Clock implements ClockInterface { 6 currentTime: Date; 7 constructor(h: number, m: number) { } 8 }
--JavaScript
1 var Clock = (function () { 2 function Clock(h, m) { 3 } 4 return Clock; 5 })();
这里我们可以看到最终的JS中并没有将currentTime作为变量加入到this中,因为在这个类中我们并没有使用到这个值,所以这个变量只会在我们正式的使用的时候添加到这个类中,如果不用这个类就等同于没有这个变量。
上面我们仅仅只是在接口中写了一个属性,下面我们在接口中增加一个方法。
--TypeScript
1 interface ClockInterface { 2 currentTime: Date; 3 setTime(d: Date); 4 } 5 6 class Clock implements ClockInterface { 7 currentTime: Date; 8 setTime(d: Date) { 9 this.currentTime = d; 10 } 11 constructor(h: number, m: number) { } 12 }
--JavaScript
1 var Clock = (function () { 2 function Clock(h, m) { 3 } 4 Clock.prototype.setTime = function (d) { 5 this.currentTime = d; 6 }; 7 return Clock; 8 })();
静态类和实例类的区别
当我们使用类和接口,需要知道类是存在静态和实例的,这也就意味着如果你的接口如果存在构造方法并且需要一个类去实现,那么你将会看到错误信息,比如下面这段。
--TypeScript
1 interface ClockInterface { 2 new (hour: number, minute: number); 3 } 4 5 class Clock implements ClockInterface { 6 currentTime: Date; 7 constructor(h: number, m: number) { } 8 }
这是因为当一个类实现一个接口的时候只有实例部分是被允许的,而构造方法恰恰属于静态,并不包含在内。
当然含有构造方法的接口是有其用途的,比如下面这样的用法。
--TypeScript
1 interface ClockInterface { 2 new (hour: number, minute: number); 3 } 4 5 class Clock { 6 currentTime: Date; 7 constructor(h: number, m: number) { } 8 } 9 10 var cs: ClockInterface = Clock; 11 var newClock = new cs(2, 3);
--JavaScript
1 var Clock = (function () { 2 function Clock(h, m) { 3 } 4 return Clock; 5 })(); 6 7 var cs = Clock; 8 var newClock = new cs(2, 3);
扩展接口
这个特性跟类可以继承其他类一样,接口也可以扩展其他的接口,这将会导致被继承的接口中的所有的内容都会被复制到另一个接口中。下面我们来看一个简单的例子。
--TypeScript
1 interface Shape { 2 color: string; 3 } 4 5 interface Square extends Shape { 6 sideLength: number; 7 } 8 9 var square = <Square>{}; 10 square.color = "blue"; 11 square.sideLength = 10;
--JavaScript
1 var square = {}; 2 square.color = "blue"; 3 square.sideLength = 10;
一个接口不仅仅只能扩展一个接口,是可以扩展多个接口的。比如下面这样。
--TypeScript
1 interface Shape { 2 color: string; 3 } 4 5 interface PenStroke { 6 penWidth: number; 7 } 8 9 interface Square extends Shape, PenStroke { 10 sideLength: number; 11 } 12 13 var square = <Square>{}; 14 square.color = "blue"; 15 square.sideLength = 10; 16 square.penWidth = 5.0;
--JavaScript
1 var square = {}; 2 square.color = "blue"; 3 square.sideLength = 10; 4 square.penWidth = 5.0;
混合类型
我们再次之前提到过,接口可以描述很多真实世界中JavaScript的类型,因为JavaScript的动态和天生的灵活性,你或许会遇到一些需要多种复合的类型。
比如下面的实例,这个对象将扮演着函数和一个对象。
--TypeScript
1 interface Counter { 2 (start: number): string; 3 interval: number; 4 reset(): void; 5 } 6 7 var c: Counter; 8 c(10); 9 c.reset(); 10 c.interval = 5.0;
--JavaScript
1 var c; 2 c(10); 3 c.reset(); 4 c.interval = 5.0;
对于需要使用第三方JavaScript库的情况下,我们就会需要使用到上面介绍的知识。当然现在很多常用的JavaScript库都已经存在了,我们可以通过nuget获取到。