[TypeScript] 编程实践之1: Google的TypeScript代码风格7:接口

TypeScript语言规范

  • 7 接口
    • 7.1 接口声明
    • 7.2 声明合并
    • 7.3 接口扩展类
    • 7.4 动态类型检查

7 接口

接口提供了命名和参数化对象类型并将现有命名对象类型组合为新对象的能力。

接口没有运行时表示—它们纯粹是编译时构造。 接口对于记录和验证所需的属性形状,作为参数传递的对象以及从函数返回的对象特别有用。

因为TypeScript具有结构类型系统,所以具有一组特定成员的接口类型被认为与具有相同成员组的另一个接口类型或对象类型文字相同(并且可以替代)(请参阅第3.11.2节)。

类声明可以在自己的Implements子句中引用接口,以验证它们是否提供了接口的实现。

7.1 接口声明

接口声明声明接口类型。

InterfaceDeclaration:
	interface BindingIdentifier TypeParametersopt InterfaceExtendsClauseopt ObjectType

InterfaceExtendsClause:
	extends ClassOrInterfaceTypeList

ClassOrInterfaceTypeList:
	ClassOrInterfaceType
	ClassOrInterfaceTypeList , ClassOrInterfaceType

ClassOrInterfaceType:
	TypeReference

InterfaceDeclaration在包含的声明空间中引入了命名类型(第3.7节)。 接口声明的BindingIdentifier可能不是预定义的类型名称之一(第3.8.1节)。

接口可以选择具有类型参数(第3.6.1节),这些类型参数用作在类型引用中引用接口时要提供的实际类型的占位符。 具有类型参数的接口称为通用接口。 通用接口声明的类型参数在整个声明的范围内,并且可以在InterfaceExtendsClause和ObjectType主体中引用。

接口可以从零个或多个在InterfaceExtendsClause中指定的基本类型继承。 基本类型必须是对类或接口类型的类型引用。

接口具有在其声明的ObjectType中指定的成员,并且还继承了接口中的声明未隐藏的所有基本类型成员:

  • 属性声明隐藏具有相同名称的公共基本类型属性。
  • 字符串索引签名声明隐藏了基本类型的字符串索引签名。
  • 数字索引签名声明隐藏了基本类型的数字索引签名。

接口声明必须满足以下约束,否则会发生编译时错误:

  • 接口声明不能直接或间接指定源自同一声明的基本类型。 换句话说,无论类型参数如何,接口都不能直接或间接成为其自身的基本类型
  • 接口不能声明与继承的私有或受保护属性同名的属性。
  • 具有相同名称的继承属性必须相同(第3.11.2节)。
  • 接口的所有属性必须满足3.9.4节中指定的接口索引签名所隐含的约束。
  • 声明的接口的this-type(第3.6.3节)必须可分配(第3.11.4节)给每个基本类型引用。

允许接口从多个基本类型继承相同的成员,并且在这种情况下,每个特定成员仅包含一个实例。

以下是两个包含名称相同但类型不同的属性的接口的示例:

interface Mover {  
    move(): void;  
    getStatus(): { speed: number; };  
}

interface Shaker {  
    shake(): void;  
    getStatus(): { frequency: number; };  
}

扩展“ Mover”和“ Shaker”的接口必须声明一个新的“ getStatus”属性,否则它将继承两个具有不同类型的“ getStatus”属性。 必须声明新的’getStatus’属性,以使生成的’MoverShaker’是’Mover’和’Shaker’的子类型:

interface MoverShaker extends Mover, Shaker {  
    getStatus(): { speed: number; frequency: number; };  
}

由于函数和构造函数类型只是包含调用和构造签名的对象类型,因此可以使用接口来声明命名的函数和构造函数类型。 例如:

interface StringComparer { (a: string, b: string): number; }

这将类型’StringComparer’声明为带有两个字符串并返回数字的函数类型。

7.2 声明合并

接口是“开放式的”,并且相对于公共根目录具有相同限定名称的接口声明(如2.3节中所定义)构成单个接口。

当通用接口具有多个声明时,所有声明必须具有相同的类型参数列表,即具有相同约束且顺序相同的相同类型参数名称。

在具有多个声明的接口中,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;  
}

请注意,最后一个接口声明的成员首先出现在合并的声明中。 还要注意,保留了在同一接口主体中声明的成员的相对顺序。

TODO:文档类和接口声明合并。

7.3 接口扩展类

接口类型扩展类类型时,它将继承该类的成员,但不继承其实现。 好像接口已经声明了类的所有成员而没有提供实现。 接口甚至继承基类的私有成员和受保护成员。 当包含私有或受保护成员的类是接口类型的基本类型时,该接口类型只能由该类或其后代类实现。 例如:

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”属性。 由于“状态”是私有成员,因此“控件”的后代只能实现“ SelectableControl”。 这是因为只有’Control’的后代才会有源自同一声明的’state’私有成员,这是私有成员兼容的要求(第3.11节)。

在“控件”类中,可以通过“ SelectableControl”的实例访问“状态”私有成员。 实际上,“ SelectableControl”的作用类似于已知具有“ select”方法的“ Control”。 “按钮”和“文本框”类是“ SelectableControl”的子类型(因为它们都继承自“控件”并且具有“选择”方法),但是“图像”和“位置”类不是。

7.4 动态类型检查

TypeScript没有提供用于动态测试对象是否实现特定接口的直接机制。 相反,TypeScript代码可以使用JavaScript技术检查对象上是否存在适当的成员集。 例如,给定第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;  
}

你可能感兴趣的:(程序设计语言)