在原生JavaScript里,是没有接口这个概念的,而TypeScript丰富并实现了这个接口。
首先,我们来看一下官方的定义:
TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
官方的概念有些拗口,而且并不能直观让读者了解接口的用法;所以,我自己根据官方文档总结出了接口的个人理解。简单来讲,接口是一种可扩展的数据类型。
接口定义
接口使用interface关键词来定义,基本定义语法为;
interface interfaceName {
// ...
}
// 例子:
interface Car {
}
接口类型
我把接口分为以下几种:
- 基本数据类型接口;
- 数组类型接口;
- 函数类型接口;
- 对象类型接口;
- 类接口;
接下来,我将按照上面的几种类型的接口分别介绍。
基本类型接口
顾名思义,基本类型接口只包含简单的数据类型,不包含复杂数据类型(数组、函数、元组等); 看下面的例子:
interface Person {
name: string,
readonly birthday: string,
age?: number,
}
let billyKing: Person
billyKing = {
name: 'billy',
birthday: '1969-07-14'
}
billyKing.birthday = '2000-00-00' // error, 只读属性,不可被修改
根据上面的例子,还没接触过TypeScript读者可能有几处比较疑惑,接下来我来一一解释。
定义接口Person的结构体里面,在birthday属性前面有一个readonly,它的作用是用来表示,birthday这个属性一旦被赋值,将不可被写入(只读属性,不可修改);所以,下面试图对birthday重新赋值的代码会报错。
而属性age的后面又有一个符号?,它用来表示age这个属性是可选属性,所以下方对变量billyKing 进行赋值的时候,没有对age进行赋值也没有报错,即用?标记的属性是可以在初始化的时候被忽略的。
而在使用Person接口的时候,语法是:let billyKing: Person,这里我们可以理解为Person是一种数据类型,即接口是一种数据类型。
总结一下:
- 接口可以视为一种数据类型;
readonly关键字可以用来标记属性是只读的,属性一旦被赋予初始值将不能再被赋值;- 符号
?可以用来表示属性是可选属性,即可以选择不对该属性进行初始化;
数组类型接口
数组类型接口,在官方文档中被称为可索引的类型的接口;我这里称为数组类型的接口是不恰当的,但是对于理解却又一定的帮助。请先看下方的例子:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
我来解释下上方的例子。上方的例子中,接口StringArray声明了一个类型的索引,即属性名为number类型,属性值类型为string类型;最终myArray被赋值为了一个字符串数组,但实际上,上面的代码也可以这么写:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = {
0: "Bob",
1: "Fred"
}
let myStr: string = myArray[0];
也可以这么写:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = {
"0": "Bob",
"1": "Fred"
}
let myStr: string = myArray[0];
从上面的例子对比,可以得出几个结论:
stringArray实际上可以定义为一组数量不定的属性名为number类型,属性值为string类型的键值对。属性名可以由number转型为string,但是不能由string转换为number。
那如果在上面的例子中混入一个普通类型的属性呢?
interface StringArray {
[index: number]: string;
length: number
}
let myArray: StringArray;
myArray = {
"0": "Bob",
"1": "Fred",
length: 2,
}
本来可以通过数组来进行赋值的变量,只能通过键值对的对象的形式来赋值了,因为属性length是字符串。
函数类型接口
通过上面的数组类型接口的,其实可以看出,接口定义的内容说到底就是一组键值对数据形式;而函数类型的接口也是如此。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
上面的例子中,接口SearchFunc的结构体内部实际上是一个函数签名。实现这个接口的时候也需要遵循函数签名中参数类型和返回值类型相同,但是形参名不需要相同。
对象类型接口
在官方的文档中,我这里的对象类型接口实际上是指的是混合类型接口。上面的例子,接口中可以定义一个函数、一个索引类型,多个基本类型(布尔、数字、字符串)的属性,但是如果它们混合在一起该如何定义呢?
我来丰富一下最开始的例子:
interface Person {
name: string,
readonly birthday: string,
age?: number,
jobs: {
[index: number]: string,
},
sayName: (name: string) => void
}
let billyKing: Person
billyKing = {
name: 'billy',
birthday: '1969-07-14',
jobs: ['boxer', 'model'],
sayName: (name)=>{
console.log(`myname is ${name}`)
}
}
通过上面的例子,可以看到对于索引类型和函数类型的写法需要改变;索引类型需要用{}包裹,而函数类型需要用=>指定返回值类型,而且函数被定义为属性,所以访问的时候函数名就固定为属性名。
类接口
在其他的面向对象的编程语言中,例如最典型的java,对于类而言,接口是用来实现的。在TypeScript也是如此。
类会继承接口的属性,通过给这些属性赋予初始值或者通过构造函数赋值,同时需要实现接口的方法。
interface Person {
name: string,
readonly birthday: string,
age?: number,
jobs: {
[index: number]: string,
},
sayName: (name: string) => void
}
class ChinesePerson implements Person {
name: string
readonly birthday: string
age?: number
jobs: {
[index: number]: string,
};
constructor(name: string, birthday: string, jobs: { [index: number]: string }) {
this.name = name
this.birthday = birthday
this.jobs = jobs
}
sayName (name: string) {
console.log(`my name is ${name}`)
}
}
接口是可以一级级继承的,因为接口只是定义了属性和方法,并没有实现,所以可以一级级继承。
interface Animal {
name: string
}
interface Dog extends Animal {
bark: ()=>void
}
class ADog implements Dog {
name: string
constructor(name: string) {
this.name = name
}
bark () {
console.log(`${this.name}汪汪汪`)
}
}
let myDog = new ADog('旺财')
myDog.bark() // '旺财汪汪汪'
从上面的这么多例子中,我总结一下TypeScript的接口:
- 接口可以理解为一种数据类型,它可以混合多种基本数据类型;
- 单独定义一个数组数据类型和函数类型的接口的写法与在混合数据类型中定义数组、函数的写法略有不同;
- 对于类而言,需要通过关键字
implents来实现接口,并获取到接口的属性和方法; - 接口是可以继承;
我上面的总结可能存在缺漏,也可能存在错误,我会在后续有进一步理解后进行勘误。希望这篇对你有所帮助。