TS的高级类型:
class,类型兼容,交叉类型,泛型和keyof,索引签名类型和索引查询类型,映射类型
(1)class类:
TS全面支持ES2015中引入的关键字,并为其添加了类型注解和其他语法(比如:可见性修饰等)
class 基本使用:
根据TS的类型推论,可以知道person类的实例对象p的类型是Person;
TS 中的class,不仅提供了class 的语法功能,也作为一种类型的存在
class Person {}
const p = new Person()
// 鼠标放在p上面显示 const p: Person
实例初始化:
class Person {
age: number // 第一种方式,声明成员age,类型为number,没有初始值
gender = '男' // 第二种方式,声明成员gender,并设置初始值,此时,可以省略类型注解
}
class类的构造函数:
(1)成员初始化(比如:age: number)后,才可以通过this.age 来访问
(2)需要为构造函数指定类型注解,否则会被隐式推断为any,并且构造函数不需要返回值类型
class Person {
// 实例属性也要写
age: number
gender: string
constructor(age: number, gender: string) {
// 构造函数没有返回值类型
this.age = age
this.gender = gender
}
}
const p = new Person(18, '男')
// p.age, p.gender 可以获取相关数据
类的实例方法在TS中的使用:
class Point {
x = 10
y = 10
// 方法的类型注解(参数和返回值)与函数用法相同
scale(n: number): void {
this.x *= n
this.y *= n
}
}
class类的继承:
1、extends(继承父类)--->两个类之间的关系
2、implements(实现接口)--->一个类和一个接口之间的关系
JS中只有extends,而implements是TS提供的
继承父类:
(1)通过extends关键字实现继承
(2)子类Dog继承父类Animal,则Dog的实例对象dog就同时拥有了父类Animal和子类Dog的所有属性和方法
class Animal {
move() {console.log('move')}
}
class Dog extends Animal {
name: 'hh'
bark() {console.log('汪汪')}
}
const dog = new Dog()
class 类中实现接口:
interface Singable {
name: string
sing(): void
}
class Person implements Singable {
name: 'jack'
sing() {
console.log('sing')
}
}
/*
(1)通过implements关键字让class实现接口
(2)Person类实现接口Singable意味着Person类中必须提供Singable的所有方法和属性
class类的可见性修饰符:public(公有的),protected(受保护的),private(私有的)
类成员的可见性:可以使用TS来控制class的方法或属性,对于class外的代码是否可见
1、public:表示公有的,公开的,公有的成员可以被任何地方访问,默认可见性
class Animal {
public move() {
console.log('move')
}
}
(1)在类属性或方法前面添加public关键字,来修饰该属性或方法是共有的
(2)public是默认可见性,所以,可以直接省略
2、protected:表示受保护的,仅在声明的类和子类中(非实例对象)可见
class Animal {
protected move() {
console.log('move')
}
}
class Dog extends Animal {
bark() {
console.log('汪')
this.move()
}
}
(1)在类属性或方法面前添加protected关键字,来修饰改属性或方法是受保护的
(2)在子类的方法内部可以通过this来访问父类中受保护的成员,但是,对实例不可见
3、private:表示私有的,只在当前类可见,对实例对象以及子类也是不可见的
class Animal {
private move() {
console.log('move')
}
walk() {
this.move()
}
}
(1)在类属性或方法前面添加private关键字,来修饰该属性或方法是私有的
(2)私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的
readonly只读修饰
除了可见性修饰符外,还有一个常见的修饰符就是:readonly(只读修饰符)
readonly:表示只读,用来防止在构造函数之外对属性进行赋值
class Person {
// 这个18是默认值,也可以不设置
// 只要是readonly修饰的属性,必须手动提供明确的类型
readonly age: number = 18
constructor(age: number) {
// 在constructor构造函数中为属性赋值
this.age = age
}
}
(1)使用readonly关键字修饰该属性是只读的,只能修饰属性不能修饰方法
(2)属性age后面的类型注解(比如这个number),如果不加,则age的类型为18(就是字面量类型了)
(3)接口或者{}表示的对象类型,也可以使用readonly
类型兼容性:(TS中类型系统中的一个特性)
有两种系统,一个是结构化类型系统,一个是标明类型系统
TS采用的是结构化类型系统,类型检查关注的是值所具有的形状;也就是说,在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型
class Ponit {x: number, y: number}
class Point2D {x: number, y: number}
const p: Point = new Point2D()
(1)Point和Point2D是两个同名称不同的类
(2)变量p的类型被显示标注为Point类型,但是它的值却是Point2D的实例,并且没有类型错误
(3)因为TS是结构化系统,只检查Point和Point2D的结构是否相同(相同,都具有x和y两个属性,属性类型也相同)
(4)但是,如果在标明类型系统中(比如C#,Java等),它们是不同的类,类型无法兼容
类型兼容性2:
在结构化系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法是不正确的;
更准确的说法是:对于对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给成员少的)
class Point {x: number, y: number}
class Point3D {x: number, y: number}
const p: Point = new Point3D()
(1)Point3D的成员至少与Point相同,则Ponit兼容Point3D
(2)所以,成员多的Point3D可以赋值给成员少的Point
除了class之外,TS中其他类型也存在着相互兼容的情况(接口兼容,兼容函数等。。。)
接口之间的兼容类似于class:
interface Point {x: number, y: number}
interface Point2D {x: number, y: number}
let p1: Point
let p2:Ponit2D = p1
interface Point3D {x: number, y: number, z: number}
let p3: Point3D
// 多的可以赋值给少的
p2 = p3
并且,class和interface之间也可以兼容
interface Point3D {x: number, y: number, z: number}
let p3: Point2D = new Point3D()
函数之间的兼容性比较复杂:需要考虑:1、函数个数 2、参数类型 3、返回值类型
1、参数个数,参数多的兼容参数少的(或者说,少的可以赋值给多的)
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2 = f1
(1)参数少的可以赋值给参数多的,所以f1可以赋值给f2
(2)在JS中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了TS中函数类型之间的兼容性
(3)并且因为回调函数是有类型的,所以TS会自动推导出item,index,array的类型
2、参数类型,相同位置的参数类型要相同(原始类型)或兼容(对象类型)
(1)原始类型:
type F1 = (a: number) => string
type F2 = (a: number) => string
let f1: F1
let f2: F2 = F1
说明:函数类型F2兼容函数类型F1,因为F1和F2的第一个参数类型相同
(2)对象类型:
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 = f2
说明:这个和前面写的那个接口兼容性冲突,将对象拆开,把每个属性看做一个个参数,则参数少的(f2)可以赋值给参数多的(f3)
(3)返回值类型:只关注返回值类型本身即可
// 原始类型
type F5 = () => string
type F6 = () => string
let f5: F5
let f6: F6 = f5
// 对象类型
type F7 = () => {name: string}
type F8 = () => {name: string, age: number}
let f7: F7
lei f8: F8
f7 = f8
说明:① 如果返回值是原始类型,此时两个类型要相同,比如 F5 和 F6
② 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的(比如F7和F8)
交叉类型:
功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
比如:
interface Person {name: string}
interface Contact {phone: number}
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name: 'jack'
phone: '133.....'
}
说明:使用交叉类型后,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型;
相当于:
type PersonDetail = {
name: string
phone: string
}
交叉类型(&)和接口继承(extends)的对比:
相同点:都可以实现对象类型的组合
不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同
(1)继承(extends)
interface A {
fn:(value: number) => string
}
// 这个B这会报错,(类型不兼容)
interface B extends A {
fn:(value: number) => string
}
(2)交叉类型(&)
interface A {
fn: (value: number) => string
}
interface B {
fn: (value: number) => string
}
type C = A & B
说明:交叉类型没有错误,可以简单的理解为:
fn: (value: number | string) => string
let c: C
c.fn() // 这个就相当于方法的重载