类型兼容性
在 TypeScript 中,对象类型兼容性是指当一个对象赋值给另一个对象时,是否满足类型要求。TypeScript 的类型兼容性是基于结构子类型而不是名义类型的,这意味着只要源类型的属性和方法满足目标类型的要求,就认为两个类型是兼容的。
TypeScript 的对象类型兼容性规则如下:
- 源类型必须具有目标类型中的所有必需属性。
- 源类型的可选属性必须在目标类型中也存在,并且类型匹配。
- 源类型不能有目标类型中不存在的属性。
- 目标类型可以有额外的属性,但是源类型的属性必须是目标类型属性的子集。
两种类型系统:1StructuralType System (结构化类型系统)2 NominalType System (标明类型系统)。
TS 采用的是结构化类型系统,也叫做 ducktyping (鸭子类型…?什么???你不明白什么叫鸭子类型? 伟大的鲁迅先生说过如果一个东西,看起来是鸭子,叫起来像鸭子,吃起来像鸭子,那它就是鸭子!!!),
类型检查关注的是值所具有的形状。
也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。
class Point x: number; y: number
class Point2D { x: number; y: number
const p: Pointt= new Point2DC
解释:
- Point和Point2D是两个名称不同的类
- 变量p的类型被显示标注为Point类型,但是,它的值却是Point2D 的实例,并且没有类型错误。
- 因为TS 是结构化类型系统,只检查Point和 oint2D 的结构是否相同(相同,都具有和y 两个属性,属性类型也相同)。
- 但是,如果在NominalType System 中(比如,C#、Java等),它们是不同的类,类型无法兼容。
对象间的兼容性
tips
:在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确。
更准确的说法: 对于对象类型来说,y 的成员至少与相同,则X 兼容y(成员多的可以赋值给少的)。
class Point x: number; y: number
class Point3D [ x: number; y: number; z: numberconst p:
Point = new Point3()
解释:
Point3D的成员至少与Point相同,则Point兼容Point3D
2.所以,成员多的Point3D可以赋值给成员少的Point。
接口之间的兼容性
除了class 之外,TS中的其他类型也存在相互兼容的情况,包括:1接口兼容性2函数兼容性等接口之间的兼容性,类似于 class。并且,class和 interface 之间也可以兼容。
//声明一个拥有两个number成员接口Point
interface Point { x: number; y: number }
//声明一个拥有两个number成员接口Point2D
interface Point2D { x: number; y: number }// 声明一个变量p1,类型为Point
let p1: Point = { x: 10, y: 20 };
//声明一个变量p2 类型为ponit2D,并将p1赋值给它
// 可以看到并没有报错
// 因为接口也拥有兼容性,只要结构一致
let p2: Point2D = p1//再声明一个拥xyz有三个number成员接口的Point3D
interface Point3D { x: number; y: number; z: number }
//声明一个Point3D类型变量p3
let p3: Point3D = { x: 10, y: 20, z: 30 }
//将p3赋值给p2
p2 = p3// 再声明一个Point3D类
class Point3D { x: number; y: number; z: number }//声明一个Point2D类型变量p4,将Point3D实例赋值给它
// 可以看到也没有报错
let c3: Point2D = new Point3D()
函数之间的兼容性
函数之间兼容性比较复杂,需要考虑:
- 参数个数
- 参数类型
- 返回值类型
参数个数 参数多的兼容参数少的(或者说,参数少的可以赋值给多的) 示例如下
// 定义一个名为 "F1" 的函数类型:没有返回值,只有一个number参数
type F1 = (a: number) => void
// 定义一个名为 "F2" 的函数类型:没有返回值,只有两个number参数
type F2 = (a: number, b: number) => void;
// 声明变量f1:类型注解为F1并初始化
let f1: F1 = (a: number) => { }
//声明一个变量f2:类型注解为f2,再将f1赋值给f2
// 可以看到并没有报错
let f2: F2 = f1// 声明一个数组arr
const arr = ['a', 'b', 'c']
// 调用数组的foreach函数并传入一个无参箭头函数
arr.forEach(() => { })
// 调用数组的foreach函数并传入一个一个参数的箭头函数
// 可以看到因为函数的类型兼容性,多个参数的函数能兼容参数少的函数
// 所以这里没有报错
arr.forEach(x => { })
- 参数少的可以赋值给参数多的,所以,f1 可以赋值给f2。
- 数组 forEach 方法的第一个参数是回调函数,该示例中类型为: (value:string,index: number, array: stringl)=>void.
- 在J5中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了T5中函数类型之间的兼容性
- 并且因为回调函数是有类型的,所以,TS会自动推导出参数item、index、array的类型。
参数类型 相同位置的参数类型要相同(原始类型)或兼容(对象类型)
如下我们可以看到,当我们将拥有不同参数类型的f1赋值给f3时报错了
解释:函数类型F2兼容函数类型F1,因为F1和F2的第一个参数类型相同。
返回值类型** 只关注返回值类型本身即可**
如果返回值类型是原始类型,此时两个类型要相同,比如,左侧类型F5 和 F6。
如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,比如,右侧类型F7 和 F8。