TypeScript学习笔记一:接口(译)
接口
接口能够命名和参数化对象类型,也可以把已存在的命名对象类型组合成一个新的类型。
接口没有运行时表现,它是一个纯编译时结构。接口用于记录和验证属性、作为参数传递的对象和函数返回的对象的所需类型。
因为ts有一个结构类型系统,所以一个具有特定成员集合的接口类型可以通过相同的成员集合来替代其他接口类型或对象类型。
类声明可以在其实现子句中引用接口以验证它们提供了一个接口的实现。
7.1 接口声明
接口声明声明一个接口类型
InterfaceDeclaration:
interface BindingIdentifier TypeParametersopt InterfaceExtendsClauseopt ObjectType
InterfaceExtendsClause:
extends ClassOrInterfaceTypeList
ClassOrInterfaceTypeList:
ClassOrInterfaceType
ClassOrInterfaceTypeList , ClassOrInterfaceType
ClassOrInterfaceType:
TypeReference
接口声明:
interface 绑定标识符 类型参数列表(可选) 接口扩展子句(可选) 对象类型
接口扩展子句:
extends 类或接口类型列表
类或接口类型列表:
类或接口类型
类或接口类型列表, 类或接口类型
类或接口类型:
类型引用
接口声明在包含的声明空间中引入一个命名类型。标识符不能与预定义类型名相冲突。
一个接口可选地具有类型参数。具有类型参数的接口被称为泛型接口。泛型接口的类型参数的作用域是整个声明,可以被接口子句和对象类型引用。
接口可以通过指定接口扩展子句来继承零个或多个基类型。基类型必须是类或接口的类型引用。
一个接口具有指定到对象类型的成员,并且继承所有的没被声明隐藏的基类型成员:
-
- 一个特性声明隐藏了一个同名的公共基类型特性
- 一个字符串索引签名声明隐藏了一个基类字符串索引签名。
- 一个数字索引签名声明隐藏了一个基类数字索引签名。
一个接口声明必须满足以下约束,否则会编译时报错:
-
- 接口不能直接或间接的成为自身的基类,也不能是自身的类型参数。
- 接口不能声明一个与接口名同名的私有或保护特性。
- 继承的同名特性必须是同一个特性。
- 接口的所有特性必须满足接口索引签名暗含的约束。
- 接口的“this”类型必须能够分配到每一个基类型引用。
一个接口可以继承来自多个基类型的相同成员。在这种情况下,一个特定成员只被包含一次。下例中,两个接口包含了不同类型的同名特性。
interface Mover {
move(): void;
getStatus(): { speed: number; };
}
interface Shaker {
shake(): void;
getStatus(): { frequency: number; };
}
一个扩展“Mover”和“Shaker”的接口必须声明一个新的“getStatus“特性,否则它将继承两个不同类型的”getStatus“特性。新的”getStatus“特性必须声明为”Mover“和”Shaker“的共同子类型。
interface MoverShaker extends Mover, Shaker {
getStatus(): { speed: number; frequency: number; };
}
由于函数和构造器类型只是包含调用和构造签名的对象类型,所以接口可以被用于声明命名函数和构造器类型。
interface StringComparer { (a: string, b: string): number; }
7.2 声明合并(Declaration Merging)
接口是“开放式”的,同名的接口声明相对于同一个根并且作用于同一接口。
当一个泛型接口具有多个声明时,所有声明必须具有相同的类型参数列表。即相同约束、相同顺序的相同名称的类型参数。
在具有多个声明的接口中,“extends”子句被合并到一个基类型集合中,接口声明被合并到一个对象类型中。声明合并的方式与栈结构类似,即最后的接口声明存在于合并声明队列的最前方。
例如,下面顺序的一系列声明:
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;
}
等同于下面的单一声明:
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
需要注意的是每个接口声明中的成员的相对位置不变。
7.3 接口扩展类(Interfaces Extending Classes)
当接口类型扩展一个类类型时,它继承了类的成员而并没有实现这个类。就好像这个接口已经声明了类的所有的成员而没有实现。接口能继承基类的私有和保护成员。如果一个接口类型的基类包含了私有或保护成员,那么只能用类或子类来实现这个接口类型。例如:
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control {
select() { }
}
class TextBox extends Control {
select() { }
}
class Image extends Control {
}
class Location{
select(){ }
}
上例中,”SelectableControl”具有“Control”的所有成员,包括私有的“state”特性。由于“state”是一个私有成员,所以它只能通过“Control”的子类来实现“SelectableControl”。
我们可以通过“SelectableControl”的实例来访问“Control”的私有成员“state”。“SelectableControl”就像拥有“select”方法的“Control”。“Button”和“TesxBox“类是”SelectableControl“的子类型(因为他们都继承自”Control“,拥有”select“方法),但”Image“和”Location“类却不是。
7.4 动态类型检测
ts不能动态测试一个对象是否实现了一个特定的接口。但是ts可以运用js的检测技术来检测一个对象上是否存在合适的成员集合。例如下例是7.1中“MoverShaker"接口的动态检测:
var obj: any = getSomeObject();
if (obj && obj.move && obj.shake && obj.getStatus) {
var moverShaker = <MoverShaker> obj;
...
}
我们可以把上述检测抽象为一个函数:
function asMoverShaker(obj: any): MoverShaker {
return obj && obj.move && obj.shake && obj.getStatus ? obj : null;
}

浙公网安备 33010602011771号