TypeScript 中接口详解
在TypeScript中,接口是用作约束作用的,在编译成JavaScript的时候,所有的接口都会被擦除掉,因为JavaScript中并没有接口这一概念。
先看看一个简单的例子:
functionprintLabel(labelledObj:{label:string}){ console.log(labelledObj.label); } varmyObj={size:10,label:"Size10Object"}; printLabel(myObj);
那么在该方法中,labelledObj的类型就是{label:string},看上去可能有点复杂,但我们看见看看下面myObj的声明就知道,这是声明了一个拥有size属性(值为10)和label属性(值为"Size10Object")的对象。所以方法参数labelledObj的类型是{label:string}即表明参数拥有一个string类型的label属性。
但是,这么写的话,这个方法看上去还是有点让人糊涂。那么就可以用接口(interface)来定义这个方法的参数类型。
interfaceLabelledValue{ label:string; } functionprintLabel(labelledObj:LabelledValue){ console.log(labelledObj.label); } varmyObj={size:10,label:"Size10Object"}; printLabel(myObj);
可选属性
有些时候,我们并不需要属性一定存在,就可以使用可选属性这一特性来定义。
interfaceSquareConfig{ color?:string; width?:number; } functioncreateSquare(config:SquareConfig):{color:string;area:number}{ varnewSquare={color:"white",area:100}; if(config.color){ newSquare.color=config.color; } if(config.width){ newSquare.area=config.width*config.width; } returnnewSquare; } varmySquare=createSquare({color:"black"});
那么我们就传入了实现一个SquareConfig接口的对象入createSquare方法。
既然完全是可有可无的,那么为什么还要定义呢?对比起完全不定义,定义可选属性有两个优点。1、如果存在属性,能约束类型,这是十分关键的;2、能得到语法智能提示,假如误将方法体中color写成collor,那么编译是不通过的。
方法类型
在JavaScript中,方法function是一种基本类型。在面向对象思想中,接口的实现是靠类来完成的,而function作为一种类型,是不是能够实现接口呢?答案是肯定的。
在TypeScript中,我们可以使用接口来约束方法的签名。
interfaceSearchFunc{ (source:string,subString:string):boolean; } varmySearch:SearchFunc; mySearch=function(source:string,subString:string){ varresult=source.search(subString); if(result==-1){ returnfalse; } else{ returntrue; } }
上面代码中,我们定义了一个接口,接口内约束了一个方法的签名,这个方法有两个字符串参数,返回布尔值。在第二段代码中我们声明了这个接口的实现。
需要注意的是,编译器仅仅检查类型是否正确(参数类型、返回值类型),因此参数的名字我们可以换成别的。
varmySearch:SearchFunc; mySearch=function(src:string,sub:string){ varresult=src.search(sub); if(result==-1){ returnfalse; } else{ returntrue; } }
这样也是能够编译通过的。
数组类型
在上面我们在接口中定义了方法类型,那么,数组类型又应该如何定义呢?很简单。
interfaceStringArray{ [index:number]:string; } varmyArray:StringArray; myArray=["Bob","Fred"];
那么myArray就是一个数组,并且索引器是number类型,元素是string。
在接口的定义里面,索引器的名字一般为index(当然也可以改成别的,但一般情况下都是保持名字为index)。所以改成
interfaceStringArray{ [myIndex:number]:string; } varmyArray:StringArray; myArray=["Bob","Fred"];
也是ok的。
需要注意的是,索引器的类型只能为number或者string。
interfaceArray{ [index:number]:any; } interfaceDictionary{ [index:string]:any; }
上面两段都是可以编译通过的。
最后还有一点要注意的是,如果接口已经是数组类型的话,接口中定义的其它属性的类型都必须是该数组的元素类型。例如:
interfaceDictionary{ [index:string]:string; length:number;//error,thetypeof'length'isnotasubtypeoftheindexer }
那么将无法编译通过,需要将length改成string类型才可以。
使用类实现接口
一般情况下,我们还是习惯使用一个类,实现需要的接口,而不是像上面直接用接口。
interfaceClockInterface{ currentTime:Date; } classClockimplementsClockInterface{ currentTime:Date; constructor(h:number,m:number){} }
在TypeScript中,使用class关键字来声明了,这跟EcmaScript6是一样的。
另外,我们可以使用接口来约束类中定义的方法。
interfaceClockInterface{ currentTime:Date; setTime(d:Date); } classClockimplementsClockInterface{ currentTime:Date; setTime(d:Date){ this.currentTime=d; } constructor(h:number,m:number){} }
在TypeScript中,我们可以为接口定义构造函数。
interfaceClockInterface{ new(hour:number,minute:number); }
接下来天真的我们可能会接着这么写:
interfaceClockInterface{ new(hour:number,minute:number); } classClockimplementsClockInterface{ currentTime:Date; constructor(h:number,m:number){} }
这是不行的!!!因为构造函数是static(静态)的,而类仅能够实现接口中的instance(实例)部分。
那么这个接口中定义的构造函数岂不是没作用?既然TypeScript提供了这项功能,那么肯定不会是没作用的。声明的方法比较特殊:
interfaceClockStatic{ new(hour:number,minute:number); } classClock{ currentTime:Date; constructor(h:number,m:number){} } varcs:ClockStatic=Clock; varnewClock=newcs(7,30);
正常情况下我们是写newClock的,这里就将Clock类指向了ClockStatic接口。需要注意的是,newClock变量的类型是any。
继承接口
像类一样,接口也能实现继承,使用的是extends关键字。
interfaceShape{ color:string; } interfaceSquareextendsShape{ sideLength:number; } varsquare=<Square>{}; square.color="blue"; square.sideLength=10;
当然也能继承多个接口。
interfaceShape{ color:string; } interfacePenStroke{ penWidth:number; } interfaceSquareextendsShape,PenStroke{ sideLength:number; } varsquare=<Square>{}; square.color="blue"; square.sideLength=10; square.penWidth=5.0;
需要注意的是,尽管支持继承多个接口,但是如果继承的接口中,定义的同名属性的类型不同的话,是不能编译通过的。
interfaceShape{ color:string; test:number; } interfacePenStroke{ penWidth:number; test:string; } interfaceSquareextendsShape,PenStroke{ sideLength:number; }
那么这段代码就无法编译通过了,因为test属性的类型无法确定。
同时使用上面所述的类型
如果仅能单一使用某种类型,那么这接口也未免太弱了。但幸运的是,我们的接口很强大。
interfaceCounter{ (start:number):string; interval:number; reset():void; } varc:Counter; c(10); c.reset(); c.interval=5.0;
这样就使用到三种类型了,分别是方法(接口自己是个方法)、属性、方法(定义了方法成员)。
以上所述就是本文的全部内容了,希望大家能够喜欢。