Typescript - 基础(三)
接口、函数、类
1、接口
作用:可以用来约束函数、对象以及类的结构和类型。
1.1 对象类型的接口
interface List { // 定义接口 list
id: number;
name: string;
}
interface Result { // 定义接口 result,由 list 数组组成
data: List[]
}
function render(result: Result) { // 渲染方法
result.data.forEach((value) => {
console.log(value.id, value.name)
})
}
let result = { // 假定接口返回的数据
data: [
{id: 1, name: 'A'},
{id: 2, name: 'B'}
]
}
render(result)
考虑其他情况:
1.1.1 result 返回数据如下:
let result = { // 假定接口返回的数据
data: [
{id: 1, name: 'A', sex: 'male'},
{id: 2, name: 'B'}
]
}
此时,ts 并没有报错,ts 允许这种情况发生的。主要是因为,如果传入的对象,满足接口的必要条件,那么就是被允许的。即便有多余的字段,也可以通过类型检查。
如果 render 方法中直接传入对象字面量的话, ts 就会对额外的参数进行类型检查。绕过这种检查的方法一共有三种:
- a. 如上,将对象字面量赋值给变量
- b.使用类型断言
// 类型断言的含义是,告诉编译器,我们使用的类型是 Result
render({
data: [
{id: 1, name: 'A', sex: 'male'},
{id: 2, name: 'B'}
]
} as Result)
// 上述方法的等价写法,但是不建议使用
render(<Result>{
data: [
{id: 1, name: 'A', sex: 'male'},
{id: 2, name: 'B'}
]
})
- c: 使用字符串索引签名
interface List { // 定义接口 list
id: number;
name: string;
[x: string]: any; // 中括号内定义一个 x,返回值类型是 any,含义是用任意的字符串索引 List,以使 List 支持多个属性
}
1.1.2 不确定数据,使用可选属性
如果 List 中可能返回 age 也可能不返回,则可改写为:
interface List {
id: number;
name: string;
age?: number;
}
1.1.3 只读属性
interface List {
readonly id: number;
name: string;
age?: number;
}
// 如果在 function render(){} 中对某一个 id 进行修改,则会报错
1.1.4 可索引类型接口
当接口中的数据个数不确定的时候,可以使用。可以用数字/字符串进行索引。
- a.数字索引的接口
interface StringArray {
[index: number] : string;
}
// 用任意的数字去索引 StringArray 都可以得到一个 string,相当于声明了一个字符串数组
let arr: StringArray = ['a', 'b'];
- b.字符串索引的接口
interface Names {
[x: string] : string;
}
let arr: StringArray = ['a', 'b'];
// 用任意的数字去索引 Names 都可以得到一个 string,相当于声明了一个字符串数组
// 如果在里面再声明一个数字类型的数据,是不被允许的,如:
interface Names1 {
[x: string] : string;
y: number; // 会报错
}
// 如果新增一个数字索引,但数据类型是 string ,是被允许的。
1.2 函数类型的接口
用变量定义函数类型:let add = (x:number, y:number) => number;
除此之外,还可以用接口定义函数类型。
interface Add {
(x:number, y:number) :number;
}
// 类型别名定义,更加简单
type Add = (x:number, y:number) => number;
let add1: Add = (a, b) => a+b;
1.3 混合类型的接口
一个接口,既可以定义一个函数,又像对象一样,拥有属性和方法:
interface Lib {
():void; // 函数
version: string; // 属性
fn():void; // 方法
}
function getlib() {
let lib:Lib = (() => {}) as Lib;
lib.version = '1.1.1.1';
lib.fn = () => {};
return lib;
}
let lib1 = getlib();
2 函数
2.1 目前函数定义的几种方法:
function add1(x: number, y: number) {
return x + y
}
let add2: (x: number, y: number) => number
type add3 = (x: number, y: number) => number
interface add4 {
(x: number, y: number): number
}
后三种只是函数类型的定义,并没有具体的实现。
2.2 函数重载
在 C/C++ 中,两个函数,如果函数名称相同,但是参数个数/类型不同,那么就实现了函数重载。不需要为功能相似的函数,采用不同的函数名称。
ts 的重载,要求先定义出一系列函数名称相同的声明。
function add (...rest:number[]):number;
function add (...rest:string[]): string;
function add (...rest: any): any {
let type = typeof rest[0];
if(type === 'string') return rest.join('');
if(type === 'number') return rest.reduce((pre,cur)=> pre + cur);
}
3 类
3.1 类的继承
// 普通 class
class Dog {
constructor(name: string) { // 构造函数的参数添加类型注解
this.name = name;
}
name: string; // 为成员属性添加类型注解
run () {};
}
// 类成员的属性都是实例属性,而不是原型属性
// 类成员的方法同理
// class 的继承
class Husky extends Dog {
constructor (name: string, color: string) {
super(name); // 代表父类的实例
this.color = color;
}
color: string;
}
3.2 类的成员修饰符
- public : 公有成员,类的所有属性默认都是 public,对所有人都是可见的,上述的类可写为:
class Dog {
constructor(name: string) {
this.name = name;
}
public name: string;
public run () {};
}
- private: 私有成员,只能在类的本身被调用,不能被类的实例调用,也不能被子类调用
class Dog {
constructor(name: string) {
this.name = name;
}
public name: string;
public run () {};
private fn () {}
}
let dog = new Dog();
dog.fn(); // 此处会报错,子类同理
如果在构造函数处添加 private,那么表明这个类 既不能被实例化也不能被继承。
- protected: 受保护成员,只能在类或者子类中被访问,而不能在实例中被访问
构造函数也可被声明为 protected,表示这个类不能被实例化,只能被继承。 - readonly: 只读属性,属性不可被更改,且必须要初始化。
- 构造参数的成员也可以添加修饰符,作用:将参数自动变成实例的属性
// 此处的 class 定义跟直接定义 name 属性的效果一致
class Dog {
constructor(public name: string) {
this.name = name;
}
}
- static 修饰符: 类的静态成员,只能通过类名来调用
class Dog {
constructor(public name: string) {
this.name = name;
}
static food: string = 'meat';
}
let dog = new Dog();
console.log(Dog.food); // meat
console.log(dog.food); // error
静态成员可以被继承,可以通过子类直接调用。
3.2 抽象类和多态
3.2.1 抽象类
ES 中没有引入抽象类的概念,这个是 TS 对 ES 的扩展。抽象类是只能被继承不能被实例化的类。
// 定义抽象类
abstract class Animal {
eat() {
console.log('eat');
}
abstract sleep ():void; // 定义一个方法,但是不指定其具体的实现,子类可以具体去实现
}
let cat = new Animal(); // Error
class Dog extend Animal {
constructor(name: string) {
super();
this.name = name;
}
name: string;
run () {};
sleep () {
console.log('dog sleep');
}
}
let dog = new Dog('wangwang');
dog.eat(); // eat
3.2.2 多态
在父类中定义一个抽象方法,在多个子类中对这个方法有不同的实现,在程序运行时,针对不同的对象,进行不同的操作,以实现运行时绑定。
// 根据 3.2.1 的示例继续写
class Cat extends Animal{
sleep() {
console.log('cat sleep');
}
}
let cat = new Cat();
let anmials : Animal[] = [dog, cat];
anmials.forEach(i => i.sleep()); // 'dog sleep', 'cat sleep'
实现一个简单的链式调用:
class workFlow {
step1 () {
return this;
};
step2 () {
return this;
}
}
new workFlow().step1().step2();
// 实现父类和子类之间的连贯调用
class myFlow extends workFlow {
next() {
return this;
}
}
new myFlow().next().step1().next().step2();
4、类与接口的关系
4.1 类类型接口
// 类类型接口
interface Human { // 定义一个接口,接口约束类成员有哪些属性以及类型
name: string;
eat(): void;
}
class Asian implements Human {
constructor(name: string) {
this.name = name;
}
name: string
eat() {}
sleep () {} // name 和 eat 是必须的属性和方法, sleep 为 class 自身定义的,是被允许的
}
类类型接(interface Human)口必须具有以下特性:
- 类实现接口的时候,必须声明接口中所有的属性
- 接口只能约束类的公有成员,如果将类中的 name 声明为
private name
,那么将报错 - 接口不能约束类的构造函数
4.2 接口继承接口
接口可以相互继承,且一个接口可以继承多个接口
interface Human { //
name: string;
eat(): void;
}
interface Man extends Human {
run(): void
}
interface Child {
cry(): void
}
interface Boy extends Man, Child {} // Boy 接口同时继承 Man 和 Child
let boy: Boy = { // Boy 需要包含所有继承的属性和方法
name: '',
run() {},
eat() {},
cry() {}
}
4.3 接口继承类
接口把类的成员全部都抽象出来,只有类的成员结构,无具体的实现。
class Auto { // 定义类
state = 1
}
interface AutoInterface extends Auto {} // 接口继承类,此时,接口中隐藏 state 属性
// 若想实现 AutoInterface 接口,只要类的成员有 state 属性即可
class C implements AutoInterface {
state1 = 1
}
// Auto 的子类也可以实现 AutoInterface 接口
class Bus extends Auto implements AutoInterface {}
// 因为 Bus 为 Auto 的子类,继承了 state 的属性
接口在抽离类的成员时,不仅仅抽离的公共成员,而且抽离了私有成员和受保护成员。如给 class Auto 添加 private state2 = 1
时,那么 class 则会报错
关于类和接口的关系可总结如下:
- 接口之间可以相互继承,extends
- 类之间可以相互继承, extends
- 接口可以通过类实现,接口只能约束类的公有成员, implements, public
- 接口可以抽离类的成员,抽离的成员包括:公有/私有/受保护成员(public/private/protected)