接口
对象类型接口
- 先用interface定义一个List接口,成员有id时number类型,name是string类型
- 再定义一个Result,成员是List数组
- 定义一个render函数,接收参数是result
// 先用interface定义一个List接口
interface List {
id:number,
name:string,
}
// 再定义一个Result,成员是List数组
interface Result {
data: List[]
}
// 定义一个render函数
function render(result:Result) {
result.data.forEach((value) => {
console.log(value.id, value.name)
})
}
// 假设从后端拿到的result数据,符合我们的定义,打印出来的结果符合我们的预期
let result = {
data:[
{id:1,name:'a'},
{id:2,name:'b'},
]
}
render(result)
- 打印出来的结果符合我们的预期
- 但是实际开发过程中,后端给的数据往往传过来预定之外的字段
检查原则
- 这样ts并不会报错,因为这是鸭式辨型法:如果一只鸟看起来像鸭子,游起来像鸭子,叫起来像鸭子,那么这只鸟就可以被认为是鸭子
- 所以只要传入的对象满足接口的必要条件,那就是允许的,也就不会报错,即使传入多余的类型字段,也可以通过类型检查。
- 但是如果你是直接传入类型字面量的话,ts就会对额外的字段进行类型检查
如何绕过这种类型检查
- 将对象字面量赋值给变量
- 使用类型断言
明确告诉编译器,这个对象的类型就是Result,这样编译器就会绕过类型检查,有两种类型断言方法,第二种在react中会产生歧义
- 使用字符串索引签名
用任意的字符串去索引List,可以得到任意的结果,这样List就可以支持多个属性了
对象属性
- 可选属性,加?
如果要直接判断有没有gender属性,那ts就会报错
所以就有可选属性
- 只读属性,加readonly,是不允许修改的
可索引类型接口
数字索引(相当于数组)[index:number]
interface StringArray {
[index:number]: string
}
用任意的数字去索引StringArray,相当于声明了一个字符串数组
字符串索引[x:string]
如果声明了字符串索引是不可以再声明其他类型的
两种类型可以混用
这样是既可以用字符串索引Strings,也可以用数字去索引Strings
混用时数字索引的返回值类型必须是字符串索引返回值的子类型!!!这是因为JavaScript会进行类型转换,将number转换成string,这样就能保持类型的兼容性
let ss:Strings = {
1:1,
'aa':2,
'bb':'cc',
}
ss[1]里面的数字number会被转换成字符串string
比如
但是你可以把上面的string类型改成any就可以兼容number了
函数类型接口
三种方式定义函数接口
混合类型接口
既可以定义一个函数,也可以像对象一样定义属性和方法
实现这个混合类型,发现会报错
给它加上属性和方法后发现还是报错
那就给它加上断言就不会有报错了
但是这样会有一个问题,给全局暴露了一个变量类,它是一个单例,如果想创建多个lib,那就要用一个函数封装一下,这样可以创建多个实例
函数
函数定义
- 定义方式:变量定义、function、类型别名、接口定义;这些都是定义函数类型,无函数体
- 类型要求:参数类型必须声明;返回值类型一般无需声明。
函数参数
-
参数个数:实参和形参必须一一对应
-
可选参数:必选参数不能放在可选参数后
-
默认参数:在必选参数前,默认参数不可省略;在必选参数后,默认参数可以省略
-
剩余参数
函数重载
- 静态类型语言:函数的名称相同,参数的个数或类型不同
- Typescript:预先定义一组名称相同,类型不同的函数声明,并在一个类型最宽松的版本中实现;使用相同名称和不同参数数量或类型创建多个方法的一种能力
- 函数重载真正执行的是同名函数最后定义的函数体 在最后一个函数体定义之前全都属于函数类型定义 不能写具体的函数实现方法 只能定义类型
类
基本实现
- 类中定义的属性都是实例属性,类中定义的方法都是原型方法
class Car {
constructor(name:string) {
this.name = name;
}
name:string
getColor() {}
}
console.log(Car.prototype); // 打印出来是不包含内部属性name的
打印出来是不包含内部属性name的
属性在实例上
let car = new Car('BMW');
console.log(car);
- 实例属性必须有初始值, 或在构造函数中被赋值,或为可选成员
class Car {
constructor(name:string) {
// this.name = name; // 在构造函数中被赋值
}
name:string = 'BMW' // 要给一个初始值
getColor() {}
}
继承
- 子类的构造函数中必须包含super调用
class BMW extends Car {
constructor(name:string, color:string){
super(name); // 子类的构造函数中必须包含super调用
this.color = color // 子类自己声明的变量要在super之后
}
color:string
}
成员修饰符
-
public: 对所有人可见,所有成员默认为 public
-
private:
- 只能在被定义的类中访问,不能通过实例或子类访问;
- private constructor:不能被实例化,不能被继承;
-
protected:
-
只能在被定义的类和子类中访问,不能通过实例访问;
-
protected constructor:只能被实例化,不能被继承;
-
-
readonly: 必须有初始值,或在构造函数中被赋值
-
static: 只能由类名调用,不能通过实例访问,可继承
构造函数参数中的修饰符
- 将参数变为实例属性
抽象类
-
不能被实例化,只能被继承
-
抽象方法包含具体实现 子类直接复用
-
抽象方法不包含具体实现 子类必须实现
-
-
多态
- 多个子类对父抽象类的方法有不同实现,实现运行时绑定
abstract class Robot {
move(){
console.log('move');
}
abstract speed():void
}
class Car extends Robot {
constructor(name:string) {
super()
this.name = name;
}
name:string
getColor(){}
speed(){
console.log('car speed');
}
}
let car = new Car('BMW');
car.move()
class Electrical extends Robot {
speed(){
console.log('Electrical speed');
}
}
let elec = new Electrical();
let robots:Robot[] = [car,elec]
robots.forEach(i => {
i.speed()
})
this 类型
本质上是原型链的调用
-
实现实例方法的链式调用
-
在继承时,具有多态性,保持父子类之间接口调用的连贯性
类与接口的关系
-
类实现接口的时候,必须实现接口中所有的属性
-
可以实现自己的属性
-
接口只能约束类的公有public成员
-
接口也不能约束类的构造函数
-
接口继承
-
接口可以继承接口,实现接口的复用
-
接口继承类,不能继承带有私有属性和受保护成员的类
-
总结
本篇罗列了接口、函数、类,写的非常详细了,仔细琢磨