• 接口一方面在面向对象编程中表示为行为的抽象,也可以用来描述对象的形状
  • 接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现次接口的类
  • 一个类可以继承另一个类并实现多个接口
  • 接口像插件一样是用来增强类的,而抽象类是具体类的抽象概念
  • 一个类可以实现多个接口,一个接口也可以被多个类实现,但一个类的可以有多个子类,但只能有一个父类

接口

interface 中可以用分号或者逗号分割每一项,也可以什么都不加。

对象的形状

  1. interface IPerson { //接口,个人习惯以首字母I开头。当然,也可以随意。
  2. name:string,
  3. age:number,
  4. }
  5. // p1对象必须有name、age属性,少属性或者多属性都会报错
  6. let p1:IPerson = {
  7. name: 'jack',
  8. age: 10,
  9. }
  10. console.log(p1); //{name: "jack", age: 10}

也可以直接把接口写进去

  1. let p1:{name:string,age:number} = {
  2. name: 'jack',
  3. age: 10,
  4. }

同名接口自动合并

  1. interface IPerson {
  2. name:string;
  3. getName():string; //属性值是一个方法的第一种描述方式
  4. }
  5. interface IPerson {
  6. age:number;
  7. setAge: (age:number) => void; //属性值是一个方法的第二种描述方式
  8. }
  9. let p:IPerson = {
  10. name:'jack',
  11. age:10,
  12. getName: function(){
  13. return this.name;
  14. },
  15. setAge: function(age){
  16. this.age = age;
  17. }
  18. };
  19. console.log(p); //{name: "jack", age: 10, getName: ƒ, setAge: ƒ}
  20. console.log(p.getName()); //"jack"
  21. p.setAge(20);
  22. console.log(p.age); //20

可选属性 name?:string

  1. interface IAnimal {
  2. type:string,
  3. name?:string, //可选属性
  4. }
  5. let a1:IAnimal = {type: '哺乳动物', name: '猫'};
  6. let a2:IAnimal = {type: '哺乳动物'};
  7. console.log(a1, a2);

任意属性 [key:string]:any

具体可看下面的可索引接口

  1. // 无法预先知道有哪些新的属性的时候,可以使用`[key:string]:any`, key 名字是任意的
  2. interface IPerson {
  3. readonly id:number;
  4. name:string;
  5. [key:string]:any; //属性名约束:任意字符串:属性值约束:任意类型
  6. }
  7. let p:IPerson = {
  8. id: 1,
  9. name: 'jack',
  10. age: 10,
  11. home: 'American',
  12. }

只读属性 readonly

用 readonly 定义只读属性,可以避免由于多人协作或者项目较为复杂等因素造成对象的值被重写。

  1. interface IPerson{
  2. readonly id:number;
  3. name:string;
  4. }
  5. let p:IPerson = {
  6. id: 1,
  7. name: 'jack',
  8. }
  9. p.name = 'lucy';
  10. p.id = 2; //报错,无法为 id 重新赋值,因为它是只读属性

取接口中的子接口类型

接口里的类型,可以通过类名别名的方式拿出来,但是只能用 [],不能用 .

  1. type IArrItem = {name:string, age:number};
  2. interface IPerson {
  3. arr: IArrItem[],
  4. address: {
  5. province: string,
  6. city: string,
  7. }
  8. }
  9. type ITest1 = IPerson['address'];
  10. type ITest2 = IPerson['address']['city'];
  11. let t1:ITest1 = {province: '江苏省', city: '苏州市'};
  12. let t2:ITest2 = '苏州市';
  13. console.log(t1, t2); //{province: "江苏省", city: "苏州市"} "苏州市"

可索引接口

  1. // ILikeArray 约束 key 的类型是 number,值的类型是 string
  2. interface ILikeArray {
  3. [key:number]:string;
  4. }
  5. // let p1:ILikeArray = { 'name': 'lucy', 'age': '10' }; //报错
  6. let p2:ILikeArray = { 0: 'jack', 1: 'lucy' };
  7. let p3:ILikeArray = ['jack', 'lucy']; //数组也是类似p2一样的对象,所以匹配约束
  8. // ILikeArray2 约束 key 的类型是 string,值的类型是 string
  9. interface ILikeArray2 {
  10. [key:string]:string;
  11. }
  12. // let p4:ILikeArray2 = ['jack', 'lucy']; //报错
  13. let p5:ILikeArray2 = { name: 'lucy', age: '10' };

函数类型接口

对方法传入的参数和返回值进行约束

  1. // type ISum = (a:number, b:number) => number;
  2. // 等同于
  3. interface ISum {
  4. (a:number, b:number):number; //(约束参数1, 约束参数2):约束函数返回值
  5. }
  6. let sum:ISum = (a, b) => a+b;
  7. console.log(sum(1, 2)); //3

但有时候,type就不适用了。
  1. // 约束一个函数,函数自身有个count属性,用于计数器。
  2. interface ICount {
  3. ():number;
  4. count:number;
  5. }
  6. const fn:ICount = (() => { //函数返回函数,一般要用as标识一下函数的类型,这样子不会报错。
  7. return ++fn.count;
  8. }) as ICount;
  9. fn.count = 0;
  10. console.log(fn()); //1
  11. console.log(fn()); //2
  12. console.log(fn()); //3
  1. // 约束一个对象,对象有一个 sum 属性是一个函数
  2. interface IObj {
  3. sum: (a:number, b:number) => number;
  4. }
  5. let obj:IObj = {
  6. sum: (a, b) => a+b,
  7. }

接口继承 extends

  1. interface IAnimal {
  2. type:string;
  3. }
  4. interface ICat extends IAnimal {
  5. name: string;
  6. }
  7. class Cat implements ICat {
  8. type='哺乳动物';
  9. name='cat';
  10. }
  11. let cat = new Cat();
  12. console.log(cat); //Cat {type: "哺乳动物", name: "cat"}

接口实现 implements

接口可以被类来实现。接口中的方法都是抽象(没有具体实现)的。

  1. // 类的实例类型
  2. interface Speakable {
  3. name: string;
  4. //用接口来形容类的时候,void 表示不关心返回值
  5. speak(words: string): void, //描述当前实例上的方法,或者原型上的方法
  6. }
  7. //类本身需要实现接口中的所有属性、方法
  8. class Dog implements Speakable {
  9. name!: string;
  10. speak(words:string) {
  11. console.log(words);
  12. };
  13. // 用接口来形容类的时候,void 表示不关心返回值。所以下面这样也可以。
  14. //speak(words:string):string{return 'abc'};
  15. }
  16. let dog = new Dog();
  17. dog.speak('汪汪汪'); //汪汪汪

实现多个接口,这个类需要满足所有接口的属性、方法
  1. interface ISpeak {
  2. type:string;
  3. speak():void;
  4. }
  5. interface IEat {
  6. eat():void;
  7. }
  8. class Animal implements ISpeak, IEat {
  9. type='哺乳动物';
  10. speak(){
  11. console.log('说话');
  12. };
  13. eat(){
  14. console.log('吃');
  15. }
  16. }
  17. let ani = new Animal();
  18. console.log(ani.type); // 哺乳动物
  19. ani.speak(); // 说话
  20. ani.eat(); // 吃

构造函数的类型

  • 在 typescript 中,可以用 interface 来描述类
  • 也可以使用 interface 里特殊的 new() 关键字来描述类构造函数的类型 ```typescript // 加上new之后,就是用来描述类构造函数的类型(约束类), // 分别是 new实例函数 的参数类型:返回值类型 type IPerson1 = new (name:string) => Person;

// 等同于 interface IPerson { new(name:string):Person; //这里 返回值的类型“Person” 指 “Person实例的类型” }

// WithClassName 就是 构造函数clazz 的约束类型 let instance:Person; function createInstance(clazz:IPerson, name:string){ if (instance) return instance; return new clazz(name); }

class Person{ constructor(public name:string){} }

let p = createInstance(Person, ‘jack’); console.log(p); //Person {name: “jack”}

  1. <a name="Tm3wD"></a>
  2. ##### 那如果想传什么类,就返回什么类实例的类型,怎么做?泛型
  3. ```typescript
  4. interface IPerson<T> {
  5. new(name:string):T;
  6. }
  7. // let instance:Person;
  8. function createInstance<T>(clazz:IPerson<T>, name:string){
  9. // if (instance) return instance;
  10. return new clazz(name);
  11. }
  12. class Person{
  13. constructor(public name:string){}
  14. }
  15. let p = createInstance<Person>(Person, 'jack');
  16. console.log(p); //Person {name: "jack"}

抽象类 vs 接口

  • 不同类之间公有的属性或方法,可以抽象成一个接口。
  • 抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中实现
  • 抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述,既不提供方法的实现,也不为属性进行初始化
  • 一个类只可以继承一个类或抽象类,但可以实现(implements)多个接口
  • 抽象类也可以实现接口 ``typescript // 抽象类:可以部分实现,部分由子类实现 abstract class Animal{ name:string; constructor(name:string){ this.name = name; } eat(fruit:string){ //有具体实现,不需要子类实现 console.log(${this.name} eat ${fruit}`); } abstract speak():void; //没有具体实现,需要子类实现 }

// 接口: 全部没有具体实现,需要子类去实现 interface Flying{ fly():void; } class Duck extends Animal implements Flying{ speak(){ console.log(‘汪汪汪’); } fly(){ console.log(‘我会飞’); } } let duck = new Duck(‘zhufeng’); duck.eat(‘bananer’); //zhufeng eat bananer duck.speak(); //汪汪汪 duck.fly(); //我会飞

  1. <a name="HVwVZ"></a>
  2. # type vs interface
  3. - interface 和 type 很相似,类型定义上,很多时候,用两种方式都能实现。
  4. - 如果不清楚什么时候用interface/type,能用 interface 实现,尽量就用 interface , 如果不能就用 type 。
  5. <a name="0SlnD"></a>
  6. ## 相同点
  7. <a name="9Ze1u"></a>
  8. ##### 都可以描述一个对象或者函数
  9. ```typescript
  10. //描述对象
  11. interface User{
  12. name:string;
  13. age:number;
  14. }
  15. //描述函数
  16. interface setUser{
  17. (name:string, age:number):number;
  18. }
  1. type User = {
  2. name:string;
  3. age:number;
  4. }
  5. type setUser = (name:string, age:number) => number;

都允许扩展
  • 扩展(extends)与 交叉类型(Intersection Types)。
  • interface 可以 extends,type 不允许 extends 和 implements 的,但是 type 却可以通过 交叉类型 实现 interface 的 extends 行为,并且两者并不是相互独立的。
  • 也就是说 interface 可以 extends type,type 也可以与 interface 类型交叉。
  • 虽然效果差不多,但是两者语法不同。

interface extends interface

  1. interface Name{
  2. name:string;
  3. }
  4. interface User extends Name{
  5. age:number;
  6. }

interface extends type

  1. type Name = {
  2. name:string;
  3. }
  4. interface User extends Name{
  5. age:number;
  6. }

type 与 type 交叉

  1. type Name = {
  2. name:string;
  3. }
  4. type User = Name & {
  5. age:number;
  6. }

type 与 interface 交叉

  1. interface Name{
  2. name:string;
  3. }
  4. type User = Name & {
  5. age:number;
  6. }

不同点

type可以而interface不行

type可以声明基本类型别名、联合类型、元祖等类型
  1. // 基本类型别名
  2. type Name = string;
  3. // 联合类型
  4. type PartialPointX = {x:number};
  5. type PartialPointY = {y:number};
  6. type PartialPoint = PartialPointX | PartialPointY;
  7. // 元祖类型
  8. type Data = [PartialPointX, PartialPointY];

type语句中还可以使用 typeof 获取实例的类型进行赋值
  1. // 当你想获取一个变量的类型时,使用 typeof
  2. let div = document.createElement('div');
  3. type B = typeof div;

type能使用 in 关键字生成映射类型
  1. type Keys = 'firstname' | 'surname';
  2. type FullName = {
  3. [key in Keys]:string;
  4. }
  5. let x:FullName = {
  6. firstname: 'Chaoyang',
  7. surname: 'Zhu',
  8. }
  1. type Person = {
  2. name:string;
  3. age:number;
  4. }
  5. type Full = {
  6. // 遍历Person类型所有的key
  7. [key in keyof Person]:string;
  8. }
  9. let x:Full = {
  10. name: 'jack',
  11. age: '10', //ok
  12. // age: 10, //error
  13. }

其它
  1. type StringOrNumber = string | number;
  2. type Text = string | { text: string };
  3. type NameLookup = Dictionary<string, Person>;
  4. type Callback<T> = (data: T) => void;
  5. type Pair<T> = [T, T];
  6. type Coordinates = Pair<number>;
  7. type Tree<T> = T | { left: Tree<T>, right: Tree<T> };

interface可以而type不行

interface 可以多次定义,并能够合并所有重名声明成员
  1. interface User {
  2. name: string
  3. age: number
  4. }
  5. interface User {
  6. sex: string
  7. }
  8. //合并后,等同于
  9. interface User {
  10. name: string
  11. age: number
  12. sex: string
  13. }

默认导出方式不同

  1. // inerface 支持同时声明、默认导出
  2. export default interface Config {
  3. name: string;
  4. }
  5. // type必须先声明后导出
  6. type Config2 = {name: string}
  7. export default Config2