TypeScript高级类型
class 类
class 类的基本使用
TS 中的 class,不仅提供了 class 的语法功能,也作为一种类型存在。
class Person{
age:number
gender='男' //根据值会进行类型推断
}
//p的类型是Person
const p=new Person()
p.age
p.gender
class 的构造函数
- 成员初始化(比如,age: number)后,才可以通过 this.age 来访问实例成员。
- 需要为构造函数指定类型注解,否则会被隐式推断为 any;构造函数不需要返回值类型。
class Person{
age:number
gender:string
constructor(age:number,gender:string){
this.age=age
this.gender=gender
}
}
const p =new Person(18,'男')
console.log(p.age)
console.log(p.gender)
class 的实例方法
方法的类型注解(参数和返回值)与函数用法相同。
class Point {
x=1
y=2
scale(n:number):void{
this.x*=n
this.y*=n
}
}
const p = new Point()
p.scale(20)
console.log(p.x,p.y)
class 的继承
说明:JS 中只有 extends,而 implements 是 TS 提供的。
extends 是class与class的关系
implements 是一个class和一个接口的关系
1. extends
class Animal{
move(){
console.log('走两步')
}
}
class Dog extends Animal{
name='小狗'
bark(){
console.log(this.name,'叫')
}
}
const d=new Dog()
d.move()
d.bark()
1. 通过 extends 关键字实现继承。
2. 子类 Dog 继承父类 Animal,则 Dog 的实例对象 dog 就同时具有了父类 Animal 和 子类 Dog 的所有属性和方法。
2. implements
interface Singable{
sing():void
}
//extends 是class与class的关系
//implements 是一个class和一个接口的关系
class Person implements Singable{
sing(): void {
console.log('你是我的小苹果')
}
}
const Ns=new Person()
Ns.sing()
1. 通过 implements 关键字让 class 实现接口。
2. Person 类实现接口 Singable 意味着,Person 类中必须提供 Singable 接口中指定的所有方法和属性。
类的成员可见性
1. public(公有的)
public:表示公有的、公开的,公有成员可以被任何地方访问,默认可见性。
class Animal{
public move(){ //public可直接省略
console.log('走两步')
}
}
2. protected(受保护的)
protected:表示受保护的,仅对其声明所在类和子类中可见。
实例化对象不能访问 protected 受保护的方法
class Animal{
protected move(){
console.log('走两步')
}
}
class Dog extends Animal{
bark(){
this.move() //子类访问
}
}
const dog =new Dog()
//实例对象不能访问 protected 受保护的方法
//dog.move()
dog.bark()
3. private(私有的)
private:表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的。
class Animal{
private __run__(){
console.log('Animal 内部辅助函数')
}
protected move(){
console.log('走两步')
this.__run__()
}
run(){
//只在当前类中可见
//对子类和实例对象不可见
this.__run__()
}
}
class只读修饰符(readonly)
readonly:表示属性只读,只能构造函数中赋一次值、
// class Person{
// //readonly表示只读属性
// readonly age: number=18
// constructor(age:number){
// this.age=age
// }
// setAge(){
// //this.age是只读属性、不可修改值
// //this.age=20
// }
// }
// const aa=new Person(18)
// //aa.age=22//无法分配到 "age" ,因为它是只读属性
class Person1{
// 只要是readonly修饰的属性、必须手动指定明确的类型
//否则根据类型推论可能不是我们想要的类型
readonly age=18
constructor(age:number){
//this.age=age//不能将类型“number”分配给类型“18”
}
}
interface IPerson{
readonly name:string
}
let obj:IPerson={
name:'jack'
}
//obj.name='re'//无法分配到 "name" ,因为它是只读属性
let obj1:{readonly name:string}={
name:'jack'
}
//obj.name='re22'//无法分配到 "name" ,因为它是只读属性
- 使用 readonly 关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。
- 注意:只要是readonly修饰的属性、必须手动指定明确的类型、否则根据类型推论可能不是我们想要的类型。
- 接口或者 {} 表示的对象类型,也可以使用 readonly。
类型兼容性
TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状。 也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。
类的兼容性
class Point{x:number; y:number}
class Point2D{x:number; y:number}
class Point3D{x:number; y:number;z:number}
const p:Point=new Point2D()
const p1:Point=new Point3D() //成员多的可以赋值给少的
解释:
- Point 和 Point2D 是两个名称不同的类。
- 变量 p 的类型被显示标注为 Point 类型,但是,它的值却是 Point2D 的实例,并且没有类型错误。
- 因为 TS 是结构化类型系统,只检查 Point 和 Point2D 的结构是否相同(相同,都具有 x 和 y 两个属性,属性类型也相同)。
- 但是,如果在 Nominal Type System (标明类型系统)中(比如,C#、Java 等),它们是不同的类,类型无法兼容。
接口兼容性
接口之间的兼容性,类似于 class。并且,class 和 interface 之间也可以兼容。
成员多的可以赋值给少的
interface Point{x:number; y:number}
interface Point2D{x:number; y:number}
interface Point3D{x:number; y:number;z:number}
let p1:Point
let p2:Point2D
let p3:Point3D
//正确
// p1=p2
// p2=p1
// p1=p3
//错误演示:类型 "Point" 中缺少属性 "z",但类型 "Point3D" 中需要该属性。
//p3=p1
//class 和 interface 之间也可以兼容。
class Point4D{ x:number; y:number;z:number}
p2=new Point4D
函数兼容性
1. 参数个数
参数少的可以赋值给多的
type F1=(a:number)=>void
type F2=(a:number,b:number)=>void
let f1:F1
let f2:F2
f2=f1 //少的赋值给多的
//f1=f2 //错误:多的不能给少的
// let arr=['a','b','c']
// arr.forEach(item=>{})
// arr.forEach((item,index)=>{})
// arr.forEach((item,index,arr)=>{})
在 JS 中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了 TS 中函数类型之间的兼容性。
2. 参数类型
参数类型,相同位置的参数类型要相同或兼容。
//原始类型
// type F1=(a:number)=>void
// type F2=(a:number)=>void
// let f1:F1
// let f2:F2
// f1=f2 //原始类型相同
//对象类型
interface Point2D{ x:number;y:number}
interface Point3D{x:number;y:number;z:number}
type F2=(p:Point2D)=>void
type F3=(p:Point3D)=>void
let f2:F2
let f3:F3
f3=f2 //少的参数赋值给多的
3. 返回值类型
返回值类型,只关注返回值类型本身即可
//原始类型 返回值类型相同即可
type F5=()=>string
type F6=()=>string
let f5:F5
let f6:F6
f5=f6
f6=f5
//对象类型
type F7=()=>{name:string}
type F8=()=>{name:string,age:number}
let f7:F7
let f8:F8
f7=f8 //对象属性多的可以赋值给少的
//f8=f7//错误
交叉类型 &
交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)。
基本使用
interface Person{ name:string,say():number}
interface Content{ phone:string}
type PersonDetail=Person&Content
let obj:PersonDetail={
name:'李寒松',
phone:'18773568***',
say(){return 1}
}
交叉类型与接口继承的对比
相同点:都可以实现对象类型的组合。
不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
// interface A{
// fn:(value:number)=>string
// }
// interface B extends A{
// fn:(value:number)=>string
// }
interface A{
fn:(value:number)=>string
}
interface B extends A{
fn:(value:string)=>string
}
type C=A&B
//&相当于这种类型
let c:C={
fn(value:number|string){
return ''
}
}
说明:以上代码,接口继承会报错(类型不兼容);交叉类型没有错误,