Typescript中类型兼容的实现
作者:新时代的弩力
typscript
中的类型兼容是基于结构子类型的(子类型兼容和赋值兼容),即只使用其成员来判定是兼容,这是根据JavaScript
的特性设计的,因为js中有很多匿名对象,只要y
的所有成员都能在对象x
中能找到,那么y=x
就能成立,在强类型语言如Java
中就不行。
比较对象兼容
比如在一个interface
和一个class
,只要interface
的变量p
的成员都能在class
或者一个匿名对象中能找到,那么就能把这个class
实例或匿名对象赋值给p
,如果是对象字面量会触发严格检查操作,需要将对象字面量赋值给一个变量y
,再赋值给变量p
interface Named { name: string; } class Person { name: string; } let p:Named p = new Person() p = {name:'xxxx'} // 此时会报错 age 不兼容 Named,因为对象字面量会触发严格类型检查,可以通过 as 断言或者 中间变量y p = {name:'xxxx',age:10}
函数参数兼容
函数参数也是一样,实参要兼容形参类型
两个函数兼容性
参数列表数量
判断函数x
是否能赋值给函数y
,一是从参数数量和参数类型上,函数x
参数数量少的,且对应参数类型兼容的能赋值给函数 y
函数返回值类型,协变
一是从函数的返回值类型上,函数 x 的返回值类型必须是函数 y 的返回值类型的子类,否则报错
函数参数双向协变(老版本ts)
函数的参数既能赋值父类也能赋值子类
函数参数逆变
当函数 x 的参数是 函数 y 的参数的时,函数 x 也能赋值给 y ,因为函数 y 将来调用时传入的时子类如 Dog 类,那么如果此时用函数 x 替换 函数 y ,相当于函数 x 接收了 Dog 类,而函数 x 的参数类型时 Animal 父类,那么也是兼容的。
interface Animal{ name:string } interface Dog extends Animal{ bark:()=>void } let x = (a:Animal)=>{} let y = (d:Dog)=>{} y = x // 函数参数逆变 x = y // 报错 // 因为存在这种情况 function test(fn:(d:Dog)=>void){ const dog = {name:'dog',bark(){}} fn(dog) // 当传入的fn是 (a:Animal)=>void 类型时,Animal类也能接收Dog子类,所以逆变的意义就在于此 } test(x)
枚举与数字之间的兼容
enum Status { Ready, Waiting }; enum Color { Red, Blue, Green }; let s = Status.Ready; s = 1 // enum 数字类型兼容 s = Color.Green; // Error
类
类和对象字面和接口差不多,主要区别在于类具有静态部分和实例部分,两个类实例对象之间能否赋值不在于其类是否一直,这与Java
等语言不同,A 类和 B 类的实例成员只要一致,那么就能相互赋值。但是类的私有成员和受保护成员会影响兼容性,如果目标类型包含一个私有成员,那么源类型必须包含来自同一个类的这个私有成员。 同样地,这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。
class Animal { feet: number=0; constructor(name: string, numFeet: number) { } } class Size { feet: number=0; constructor(numFeet: number) { } } let a: Animal = new Animal('',0) let s: Size = new Size(0); a = s; // OK s = a; // OK
泛型
泛型其实对于兼容性的影响在于影响其结果类型,然后在结果类型再去比较类型兼容性
如下,经过泛型推导后的类型是{},那么 x , y 的类型比较就是最终的类型比较
interface Empty<T> { } let x: Empty<number>; let y: Empty<string>; x = y; // OK, because y matches structure of x
到此这篇关于Typescript中类型兼容的实现的文章就介绍到这了,更多相关Typescript 类型兼容内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!