接口
Typescript 接口与您在 C# 和 Java 中习惯的接口具有类似的用途,但它们具有非常不同的语义。在 C# 和 Java 中,接口主要用作实现该接口的类的模板。Typescript 类也可以用于此目的,但它们也可以被认为是 JavaScript 对象的模板。
假设我们使用自动类型推断声明了以下 JavaScript 对象。
let me = { name:"Reza", age:21, address:"Gold Coast" };
我们可以声明三个简单的 Typescript 接口来匹配这一点(还有更多可能)。
interface Person1 { name:string; age: number; address:string; } ;
interface Person2 { name:string; age: number; } ;
interface Person3 { name:string; } ;
请注意此处使用分号 ';“(它不是逗号),最后一个属性后面有一个分号。
这三个接口可以在原始对象声明中使用,如下所示。
let me1 : Person1 = { name:"Reza", age:21, address:"Gold Coast" };
let me2 : Person2 = { name:"Reza", age:21 };
let me3 : Person3 = { name:"Reza" };
它们具有不同的类型检查含义。第一个将坚持初始化对象中存在所有三个属性。第二个声明只会坚持两个属性,第三个声明只会坚持一个属性。这意味着以下声明将无法通过Typescript检查。
let me1 : Person1 = { name:"Reza", age:21 };
let me2 : Person2 = { name:"Reza", age:21, hieght:92.5};
let me3 : Person3 = { name:"Reza", hieght:123};
这组 TypeScript 代码出错的原因如下:
- 在第一个代码行中,变量
me1
定义为类型Person1
,但它缺少接口Person1
中定义的属性address
。 - 在第二个代码行中,变量
me2
定义为类型Person2
,但它包含额外的属性hieght
,而接口Person2
并未定义该属性。 - 在第三个代码行中,变量
me3
定义为类型Person3
,但它缺少接口Person3
中定义的属性hieght
。
在上述每种情况下,其中一个接口成员不存在,或者用于初始化变量的对象中存在额外的元素。
虽然接口在对象初始化中的使用方式很严格,但当接口用作函数参数时,这种情况会发生变化。该行为类似于 C# 或 Java 中的多态行为。请考虑以下函数定义,该定义使用上述 Person3 接口来定义参数:
let pname = function(n:Person3):string {
return n.name;
}
以下调用都是正确的:
console.log(pname(me1));
console.log(pname(me2));
console.log(pname(me3));
Typescript 对象还允许我们指定可选属性。这与我们在上一节中看到的可选参数具有类似的语法。上述接口示例的以下变体允许年龄和地址属性是可选的。
interface Person4 { name:string; age?: number; address?:string; } ;
let me4:Person4 = { name: "Reza"}; // OK
let me5:Person4 = { name: "Reza", age:21}; // OK
Typescript 对象还允许我们指定可选属性。这与我们在上一节中看到的可选参数具有类似的语法。上述接口示例的以下变体允许年龄和地址属性是可选的。
interface Person4 { name:string; age?: number; address?:string; } ;
let me4:Person4 = { name: "Reza"}; // OK
let me5:Person4 = { name: "Reza", age:21}; // OK
要确定是否提供了可选属性,我们可以使用 if 语句。例如,以下函数检查年龄和地址属性是否存在,如果不存在,则插入一个值。
function fillPerson (p:Person4): Person1 {
let newp:Person1 =
{name: "", age:10, address: "No fixed address"};
newp.name = p.name;
if (p.age) { // optional property present
newp.age=p.age;
}
if (p.address) { // optional property present
newp.address=p.address;
}
return newp;
}
接口的另一个特点是我们可以将成员指定为只读。与变量一样,这意味着接口属性只能在初始化具有接口的对象时更新,并且 Typescript 将在任何其他时间显示错误。
例如,我们可以将后续示例中的 name 属性设为只读:
interface Person5 { readonly name:string; age?: number; address?:string; } ;
对于声明为具有接口 Person5 的任何对象,尝试更改 name 属性将导致 Typescript 语法错误。
我们还可以声明函数接口和数组接口。这些与我们到目前为止看到的并不完全相同,因为它们通常仅适用于单个函数或单个数组(而不是具有多个属性的对象)。您可以将这些视为为函数类型或数组类型分配名称的一种方式。
下面是函数接口声明的简单示例。
这将使用具有两个数字参数并返回一个数字的任何函数正确键入 check。请注意,接口中的参数名称不必与实际函数定义中的名称匹配,我们之前看到的函数类型也是如此。
可索引类型接口(例如数组)也可以类似地定义。例如,以下接口声明将针对任何数字数组(按数字索引)正确键入 check。
interface NumArray {
[index:number]: number;
}
请注意,可索引类型也可以由其他类型(例如通过字符串)进行索引。例如,以下类型是可由字符串索引的数字数组:
interface NumArray2 {
[index:string]: number;
}
对于这个数组,我们可以按任何字符串(与 C# 或 Java 非常不同)进行索引,例如:
let a1:NumArray2 = {};
a1["hippo"] = 23;
console.log(a1["hippo"]);
Typescript 类可以像其他语言一样实现接口。我们将等到我们看类后再演示。
最后,接口也可以使用继承,就像你见过的其他语言一样。例如,我们可以声明一个名为 Animal 的接口,并将其扩展到包含 Animal 所有属性的 Pet 接口。
interface Animal {
species: string;
}
interface Pet extends Animal {
name: string; // the species property is inherited to be part of this
}
如前所述,Typescript 接口与 C# 或 Java 相同,但在 Typescript 中有一些额外的应用程序。当接口与类一起使用时,您将熟悉它们。在 Typescript 中,它们也与 JavaScript 对象一起使用。
请考虑以下 Typescript 接口定义:
interface Student {ID:number; name: string; marks: number};
请注意界面的各个部分是如何用分号 “;” 分隔的,而不是用逗号 “,” 与 JavaScript 对象一起使用的。
我们可以利用这个接口来定义变量,以便 Typescript 将检查对象是否具有正确的形式。例如,以下对象变量具有正确和不正确的初始化:
let s1:Student = {ID:1234, name: "Baz", marks: -1};
let s2:Student = {ID:1234, name: "Baz" }; // error 'marks' is missing
接口类型声明仅检查指定的元素,不能像使用其他语言的接口那样添加额外的元素。例如,以下内容是不正确的:
interface Student2 {ID:number};
let s3:Student2 = {ID:1234, name: "Baz", marks: -1}; // fails
当我们使用函数参数接口时,这种严格的行为不会反映出来。您的作者认为这不是很一致。使用上述接口考虑以下函数:
let getID = function (s:Student2) {
return s.ID;
}
现在,看看 Student2 的定义,我们预计以下内容会失败,但它们是正确的。
在这两种情况下,函数参数只需要一个具有 ID 属性的对象。注意 S6 甚至不使用接口来定义对象。这与上面解释的对文字对象的严格检查形成鲜明对比。
我们还可以使用接口来防止对象属性的更改。这是通过只读关键字实现的。例如,我们可以将学生 ID 属性设为只读,这样如果我们尝试更改它,Typescript 会产生语法错误:
interface Student3 {
readonly ID:number;
name: string;
marks: number
};
为了允许接口中的可选元素,有特定的语法。假设我们想使上述 Student 界面中的名称和标记属性可选。我们在标识符后用“?”符号指定:
interface Student4 {ID:number; name?:string; marks?: number;};
然后,我们可以进行如下分配:
let s7:Student4 = {ID:1234, name: "Baz"};
let s8:Student4 = {ID:1234, marks: 23};
let s9:Student4 = {ID:1234};
我们还可以声明函数接口和数组接口。这些与我们迄今为止看到的那些不同。它们通常仅适用于单个函数或单个数组(不适用于具有多个属性的对象)。您可以将这些视为一种方式
为函数类型或数组类型指定名称。
下面是函数接口声明的简单示例。
interface BinNumFun {
(n1:number, n2:number): number;
}
这将使用具有两个数字参数并返回一个数字的任何函数正确键入 check。请注意,接口中的参数名称不必与实际函数定义中的名称匹配,我们之前看到的函数类型也是如此。因此,要使用这种函数类型,我们只需使用符号 BinNumFun。
可索引类型接口(例如数组)也可以类似地定义。例如,以下接口声明将针对任何数字数组(按数字索引)正确键入 check。
interface NumArray {
[index:number]: number;
}
请注意,可索引类型也可以由其他类型(例如通过字符串)进行索引。例如,以下类型是可由字符串索引的数字数组:
interface NumArray2 {
[index:string]: number;
}
对于这个数组,我们可以按任何字符串(与 C# 或 Java 非常不同)进行索引,例如
let a1:NumArray2 = {};
a1["hippo"] = 23;
console.log(a1["hippo"]);
Typescript 类可以像其他语言一样实现接口。我们将等到我们看类后再演示。接口也可以像以前看到的那样使用继承。