接口作用
接口用于约束类、对象、函数,是一个类型契约;
不使用接口实现时:
- 对能力(成员函数)没有强约束力;
- 容易将类型和能力耦合在一起;
面向对象领域中的接口的语义:表达了某个类是否拥有某种能力,其实,就是实现了某种接口;
扩展类型-接口
扩展类型:类型别名、枚举、接口、类;
接口:用于约束类、对象、函数的契约(标准)和 类型别名 一样,接口不出现在编译结果中;
接口约束对象
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);
接口可以继承
-
可以通过接口之间的继承,实现多种接口、类的组合;
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, }
-
使用类型别名可以实现类似的组合效果,需要通过
&
,它叫做交叉类型;type A = { T1: string } type B = { T2: number } type C = { T3: boolean } & A & B let u: C = { T1: "43", T2: 33, T3: true }
-
它们的区别:
- 子接口不能覆盖父接口的成员;
- 交叉类型会把相同成员的类型进行交叉;
readonly
只读修饰符,修饰的目标是只读;
只读修饰符不在编译结果中;
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 ,则 B 和 A 类型兼容,基本类型要完全匹配
对象类型:鸭子辨型法
-
目标对象类型需要某一些特征,赋值的类型只要能满足该特征即可 (只要长得像🦆,就是🦆)
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;
-
当直接使用对象字面量赋值的时候,会进行更加严格的判断
let duck: Duck = { name: "伪装成鸭子的人", // ❌ 对象字面量只能指定已知属性,并且“name”不在类型“Duck”中 sound: "嘎嘎嘎" as "嘎嘎嘎", swin() { console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音"); } };
函数类型兼容性
-
参数:传递给目标函数的参数可以少,但不可以多;
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);
-
返回值:要求返回必须返回;不要求返回则随意;
interface 对比 type
interface | type | |
---|---|---|
被类实现 |
可以 | 不可以 |
定义的类型 |
主要声明的是函数和对象,并且总是需要引入特定的类型 | 可以是任何的数据类型 |
扩展的方式 |
可以使用 extends 继承,implements 实现接口 | 使用 & (交叉类型) 进行扩展 |
合并声明 |
定义一个名字,后面的接口可直接使用这个名字,自动合并所有的声明 (不建议) | 不能命名,会直接报错 |
对实例赋值 |
没有这个功能 | 可以使用 typeof 获取实例的类型进行赋值 |
类型映射 |
没有这个功能 | 可以通过 in 来实现类型映射 |
案例
用接口改造扑克牌程序
// 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);
马戏团
有一个马戏团,马戏团中有很多动物,包括:狮子、老虎、猴子、狗,这些动物都具有共同的特征:名字、年龄、种类名称,还包含一个共同的方法:打招呼,它们各自有各自的技能,技能是可以通过训练改变的;狮子和老虎能进行火圈表演,猴子能进行平衡表演,狗能进行智慧表演;
马戏团中有以下常见的技能:
- 火圈表演:单火圈、双火圈
- 平衡表演:独木桥、走钢丝
- 智慧表演:算术题、跳舞
// 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();
}
})
TypeScript👉 模块化
上一篇