接口作用

  1. 接口用于约束类、对象、函数,是一个类型契约;

  2. 不使用接口实现时:

    1. 对能力(成员函数)没有强约束力;
    2. 容易将类型和能力耦合在一起;
  3. 面向对象领域中的接口的语义:表达了某个类是否拥有某种能力,其实,就是实现了某种接口;

扩展类型-接口

  1. 扩展类型:类型别名、枚举、接口、类;

  2. 接口:用于约束类、对象、函数的契约(标准)和 类型别名 一样,接口不出现在编译结果中;

接口约束对象

interface User {
    name: string
    age: number
    sayHello(): void
}

// 与上面代码等价
// type User = {
//     name: string
//     age: number
//     sayHello: () => void
// }

let u: User = {
    name: "zhangsan",
    age: 33,
    sayHello() {
        console.log("hello");
    }
}

接口约束函数

interface Condition {
    (n: number): boolean
}
// type Condition = (n: number) => boolean; // 与上面代码等价


function sum(numbers: number[], callBack: Condition) {
    let s = 0;
    numbers.forEach(n => {
        if (callBack(n)) {
            s += n;
        }
    })
    return s;
}

const result = sum([3, 4, 5, 7, 11], n => n % 2 !== 0);
console.log(result);

接口可以继承

  1. 可以通过接口之间的继承,实现多种接口、类的组合;

    interface A {
      T1: string
    }
    interface B {
      T2: number
    }
    class C {
      name: string = "";
      age: number = 20;
    }
    interface D extends A, B, C {
      T3: boolean
    }
    
    let u: D = {
      T1: "43",
      T2: 33,
      T3: true,
      name: "",
      age: 20,
    }
    
  2. 使用类型别名可以实现类似的组合效果,需要通过 &,它叫做交叉类型;

    type A = {
        T1: string
    }
    type B = {
        T2: number
    }
    type C = {
        T3: boolean
    } & A & B
    
    let u: C = {
        T1: "43",
        T2: 33,
        T3: true
    }
    
  3. 它们的区别:

    1. 子接口不能覆盖父接口的成员;
    2. 交叉类型会把相同成员的类型进行交叉;

readonly

  1. 只读修饰符,修饰的目标是只读;

  2. 只读修饰符不在编译结果中;

type User = {
    readonly id: string
    name: string
    age: number,
    readonly arr: readonly string[]
}

let u: User = {
    id: "123",
    name: "Asdf",
    age: 33,
    arr: ["Sdf", "dfgdfg"]
}
u.arr = [1, 2]; // 无法分配到 "arr" ,因为它是只读属性
u.arr.push(); // 类型“readonly string[]”上不存在属性“push”

类型兼容性

基本数据类型

如果 B 可以赋值给 A ,则 BA 类型兼容,基本类型要完全匹配

对象类型:鸭子辨型法

  1. 目标对象类型需要某一些特征,赋值的类型只要能满足该特征即可 (只要长得像🦆,就是🦆)

    interface Duck {
      sound: "嘎嘎嘎"
      swin(): void
    }
    
    let person = {
      name: "伪装成鸭子的人",
      age: 11,
      // 类型断言 sound:"嘎嘎嘎" 这样的写的话 TS 会自动推断出 sound 的类型为 string
      // 用了类型断言就是 "嘎嘎嘎" 类型的 "嘎嘎嘎" 值了;其实就是更换类型
      sound: "嘎嘎嘎" as Duck["sound"],
      swin() {
        console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音");
      }
    }
    
    // 这就是鸭子辦型法,可以完成赋值
    // 但是,不能直接把 person 的字面量对象直接写过来
    // 如果直接写过来,就更加严格了,只能是个鸭子
    let duck: Duck = person;
    
  2. 当直接使用对象字面量赋值的时候,会进行更加严格的判断

    let duck: Duck = {
      name: "伪装成鸭子的人", // ❌ 对象字面量只能指定已知属性,并且“name”不在类型“Duck”中
    
      sound: "嘎嘎嘎" as "嘎嘎嘎",
      swin() {
        console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音");
      }
    };
    

函数类型兼容性

  1. 参数:传递给目标函数的参数可以少,但不可以多;

    interface Condition {
      (n: number, i: number): boolean
    }
    
    function sum(numbers: number[], callBack: Condition) {
      let s = 0;
      for (let i = 0; i < numbers.length; i++) {
        const n = numbers[i];
        // 这里接口约束需要传递两个参数
        if (callBack(n, i)) {
          s += n;
        }
      }
      return s;
    }
    
    // 这里实际调用 callBack 的时候只传了一个参数
    const result = sum([3, 4, 5, 7, 11], n => n % 2 !== 0);
    console.log(result);
    
  2. 返回值:要求返回必须返回;不要求返回则随意;

interface 对比 type

interface type
被类实现 可以 不可以
定义的类型 主要声明的是函数和对象,并且总是需要引入特定的类型 可以是任何的数据类型
扩展的方式 可以使用 extends 继承,implements 实现接口 使用 & (交叉类型) 进行扩展
合并声明 定义一个名字,后面的接口可直接使用这个名字,自动合并所有的声明 (不建议) 不能命名,会直接报错
对实例赋值 没有这个功能 可以使用 typeof 获取实例的类型进行赋值
类型映射 没有这个功能 可以通过 in 来实现类型映射

案例

用接口改造扑克牌程序

TypeScript
TypeScript
TypeScript
TypeScript
// enums.ts
export enum Color {
    heart = "♥",
    spade = "♠",
    club = "♣",
    diamond = "♦"
}

export enum Mark {
    A = "A",
    two = "2",
    three = "3",
    four = "4",
    five = "5",
    six = "6",
    seven = "7",
    eight = "8",
    nine = "9",
    ten = "10",
    eleven = "J",
    twelve = "Q",
    king = "K"
}
// types.ts
import { Color, Mark } from "./enums";

export interface Card {
    getString(): string
}

export interface NormalCard extends Card {
    color: Color
    mark: Mark
}

export type Deck = NormalCard[]
// funcs.ts
import { Deck, NormalCard, Card } from "./types";
import { Mark, Color } from "./enums";

export function createDeck(): Deck {
    const deck: Deck = [];
    const marks = Object.values(Mark)
    const colors = Object.values(Color)
    for (const m of marks) {
        for (const c of colors) {
            deck.push({
                color: c,
                mark: m,
                getString() {
                    return this.color + this.mark;
                }
            } as Card)
        }
    }
    return deck;
}

export function printDeck(deck: Deck) {
    let result = "\n";
    deck.forEach((card, i) => {
        result += card.getString() + "\t";
        if ((i + 1) % 4 === 0) {
            result += "\n";
        }
    })
    console.log(result);
}
// index.ts
import { createDeck, printDeck } from "./funcs";

const deck = createDeck();
printDeck(deck);

马戏团

  1. 有一个马戏团,马戏团中有很多动物,包括:狮子、老虎、猴子、狗,这些动物都具有共同的特征:名字、年龄、种类名称,还包含一个共同的方法:打招呼,它们各自有各自的技能,技能是可以通过训练改变的;狮子和老虎能进行火圈表演,猴子能进行平衡表演,狗能进行智慧表演;

  2. 马戏团中有以下常见的技能:

    1. 火圈表演:单火圈、双火圈
    2. 平衡表演:独木桥、走钢丝
    3. 智慧表演:算术题、跳舞
TypeScript
TypeScript
TypeScript
// interfaces.ts
export interface IFireShow {
    singleFire(): void;
    doubleFire(): void;
}

export interface IWisdomShow {
    suanshuti(): void;
    dance(): void
}

export interface IBalanceShow {
    dumuqiao(): void;
    zougangsi(): void;
}

export function hasFireShow(ani: object): ani is IFireShow {
    if ((ani as IFireShow).singleFire && (ani as IFireShow).doubleFire) {
        return true;
    }
    return false;
}

export function hasWisdomShow(ani: object): ani is IWisdomShow {
    if ((ani as IWisdomShow).dance && (ani as IWisdomShow).suanshuti) {
        return true;
    }
    return false;
}
// animals.ts
import { IFireShow, IWisdomShow, IBalanceShow } from "./interfaces";

export abstract class Animal {
    abstract type: string;

    constructor(
        public name: string,
        public age: number
    ) {

    }

    sayHello() {
        console.log(`各位观众,大家好,我是${this.type},我叫${this.name},今年${this.age}岁`)
    }
}

export class Lion extends Animal {
    type: string = "狮子";

}

export class Tiger extends Animal implements IFireShow {
    type: string = "老虎";

    singleFire() {
        console.log(`${this.name}穿越了单火圈`);
    }

    doubleFire() {
        console.log(`${this.name}穿越了双火圈`);
    }
}

export class Monkey extends Animal implements IBalanceShow, IFireShow {
    type: string = "猴子";

    dumuqiao() {
        console.log(`${this.name}表演走独木桥`);
    }

    zougangsi() {
        console.log(`${this.name}表演走钢丝`);
    }


    singleFire() {
        console.log(`${this.name}穿越了单火圈`);
    }

    doubleFire() {
        console.log(`${this.name}穿越了双火圈`);
    }
}

export class Dog extends Animal implements IWisdomShow {
    type: string = "狗";

    suanshuti() {
        console.log(`${this.name}表演做算术题`);
    }

    dance() {
        console.log(`${this.name}表演跳舞`);
    }
}
// index.ts
import { Animal, Lion, Tiger, Monkey, Dog } from "./animals";
import { IFireShow, IWisdomShow, hasFireShow, hasWisdomShow } from "./interfaces";

const animals: Animal[] = [
    new Lion("王富贵", 12),
    new Tiger("坤坤", 21),
    new Monkey("小六", 1),
    new Dog("旺财", 3),
    new Dog("狗剩", 5)
];

//1. 所有的动物打招呼
// animals.forEach(a => a.sayHello());

//2. 所有会进行火圈表演的动物,完成火圈表演
animals.forEach(a => {
    if (hasFireShow(a)) {
        a.singleFire();
        a.doubleFire();
    }
})

//3. 所有会智慧表演的动物,完成智慧表演
animals.forEach(a => {
    if (hasWisdomShow(a)) {
        a.suanshuti();
        a.dance();
    }
})
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 接口作用
  2. 2. 扩展类型-接口
    1. 2.1. 接口约束对象
    2. 2.2. 接口约束函数
    3. 2.3. 接口可以继承
    4. 2.4. readonly
  3. 3. 类型兼容性
    1. 3.1. 基本数据类型
    2. 3.2. 对象类型:鸭子辨型法
    3. 3.3. 函数类型兼容性
  4. 4. interface 对比 type
  5. 5. 案例
    1. 5.1. 用接口改造扑克牌程序
    2. 5.2. 马戏团