接口的兼容性

  • 如果传入的变量和声明的类型不匹配,TS就会进行兼容性检查
  • 原理是 Duck-Check ,就是说只要目标类型中声明的属性变量在源类型中都存在就是兼容的 ``typescript interface Animal{ name:string; age:number; } interface Person { name:string; age:number; gender:number; } // 要判断目标类型Person是否能够兼容输入的源类型Animal` function getName(a:Animal):string{ return a.name; }

let p:Person = { name: ‘jack’, age: 10, gender: 0, } // Animal接口所需的属性,Person都有,所以getName的参数a也可以用Person接口 console.log(getName(p)); //jack

// 只有在传参的时候两个变量之间才会进行兼容性比较; // 注意:直接赋值的时候并不会进行兼容性比较。

  1. <a name="VL6HK"></a>
  2. ##### 直接赋值的时候并不会进行兼容性比较
  3. ```typescript
  4. interface Animal{
  5. name:string;
  6. age:number;
  7. }
  8. interface Person {
  9. name:string;
  10. age:number;
  11. sex:string,
  12. }
  13. let p:Person = {name: 'jack', age: 10, sex: 'male'};
  14. // 情况1:报错
  15. let a1:Animal = {name: 'jack', age: 10, sex: 'male'};
  16. // 情况2:报错
  17. let a2:Animal;
  18. a2 = {name: 'jack', age: 10, sex: 'male'};
  19. // 情况3: 可以
  20. let a3:Animal = p;
  21. // 情况4: 可以
  22. let a4:Animal;
  23. a3 = p;

基本类型的兼容性

  1. //基本数据类型也有兼容性判断
  2. let num:string|number;
  3. let str:string = 'jack'; //直接赋值的时候,不会进行兼容性比较
  4. num = str; //可以,间接赋值的时候,会进行兼容性比较,只要后者类型是前者类型的子类型,就可以。
  5. // str = num; //不可以
  6. //只要有toString()方法就可以赋给字符串变量
  7. let num2:{
  8. toString():string;
  9. }
  10. let str2:string = 'jack';
  11. num2 = str2; //可以
  12. // str2 = num2; //不可以,string上除了toString方法外,还有许多其他的方法,而这些是 num2 不具有的

类的兼容性

在TS中是结构类型系统,只会对比结构而不在意类型

  1. class Animal{name!:string;}
  2. class Bird extends Animal {age!:number;}
  3. let a:Animal = new Bird(); //可以,Bird中具有Animal的name属性,所以Bird兼容Animal
  4. // let b:Bird = new Animal(); //报错,Animal中不具有Bird所需的age属性,所以Animal不兼容Bird
  1. //如果父类和子类结构一样,也可以的
  2. class Animal{}
  3. class Bird extends Animal {}
  4. let a:Animal = new Bird(); //可以
  5. let b:Bird = new Animal(); //可以
  1. //甚至没有关系的两个类的实例也是可以的
  2. class Animal{}
  3. class Book {}
  4. let a:Animal = new Book(); //可以
  5. let b:Book = new Animal(); //可以

函数的兼容性

比较函数的时候是要先比较函数的参数,再比较函数的返回值。
一切为了类型安全,为了使用的时候不报错。

比较参数

  1. function add(x,y){
  2. // 我这边,只有2个参数
  3. // fn可以不用、用1个、用2个都行
  4. fn1();
  5. fn2(x);
  6. fn3(x,y);
  7. //但是,想要用 我都没有的,不行。
  8. fn4(x,y,z);
  9. }
  10. add(1,2);
  1. type Func = (a:number, b:number) => void;
  2. let sum:Func;
  3. // 参数一样 兼容 可以赋值给sum
  4. function f1(a:number, b:number):void{}
  5. sum = f1;
  6. // 参数少一个,甚至无参数, 兼容
  7. function f2(a:number):void{}
  8. function f3():void{}
  9. sum = f2;
  10. sum = f3;
  11. // 参数多一个, 不兼容.
  12. //sum只能传2个参数,但是f4调用的时候需要3个参数,所以报错
  13. function f4(a:number, b:number, c:number):void{}
  14. sum = f4;
  15. sum(1,2);

比较返回值

  1. // 这里只是类型定义,返回对象的话,不需要小括号包裹
  2. type GetPerson = () => {name:string, age:number};
  3. let getPerson:GetPerson;
  4. // 兼容,返回值一样可以
  5. function g1(){
  6. return {name:'jack', age: 18};
  7. }
  8. getPerson = g1;
  9. // 兼容,返回值多一个属性也可以
  10. function g2(){
  11. return {name:'jack', age: 18, gender: 0};
  12. }
  13. getPerson = g2;
  14. // 不兼容,返回值少一个属性可不行
  15. function g3(){
  16. return {name:'jack'};
  17. }
  18. getPerson = g3; //报错
  19. // 为什么这样设计,
  20. // 一切为了类型安全,为了使用的时候不报错。
  21. // 比如,有可能要调用返回值上的方法,如果没有age,age就是undefined,undefined.toFixed()就会发生错误
  22. getPerson().age.toFixed();

函数的协变与逆变

  • 协变(Covariant):只在同一个方向。
  • 逆变(Contravariant):只在相反的方向。
  • 双向协变(Bivariant):包括同一个方向和不同方向。
  • 不变(Invaraint):如果类型不完全相同,则它们是不兼容的。

  • A ≼ B 意味着 A 是 B 的子类型。

  • A → B 指的是以 A 为参数类型,以 B 为返回值类型的函数类型。

  • x : A 意味着 x 的类型为 A

  • 返回值类型是协变的,而参数类型是逆变的

  • 返回值类型可以传子类,参数可以传父类

  • 参数逆变父类 返回值协变子类 搀你父,返鞋子

    注意:在 TypeScript 中, 参数类型是双向协变的 ,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在 TypeScript 2.6 版本中通过 --strictFunctionTypes--strict 标记来修复这个问题

示例1

  1. class Animal{}
  2. class Dog extends Animal{public name!:string;}
  3. class BlackDog extends Dog {public age!:number;}
  4. let animal:Animal = {};
  5. let dog:Dog = {name: '狗'};
  6. let blackDog:BlackDog = {name: '狗', age: 10};
  7. type Cb = (dog: Dog) => Dog; // 形参cb的约束类型
  8. function exec(cb: Cb){
  9. cb(dog); //示例1
  10. return cb(blackDog).name; //示例2
  11. };
  12. // 结论:
  13. // 函数cb的参数是逆变的,可以传父类;(传逆父)
  14. // 函数cb的返回值是协变的,可以传子类。(返协子)

实参cb的约束情况
  1. // 参数是子类,返回值是子类 (报错)
  2. // 参数不安全,比如示例1中函数参数cb调用时,回传参数为 dog;但是dog没有age属性,fn1调用时,就不安全。
  3. type ChildToChild = (blackDog: BlackDog) => BlackDog;
  4. const fn1:ChildToChild = (blackDog) => blackDog;
  5. exec(fn1);
  6. // 参数是父类,返回值是父类 (报错)
  7. // 返回值不安全,比如示例2,需要有name属性,但是fn2的返回值没有name属性
  8. type ParentToParent = (animal: Animal) => Animal;
  9. const fn2:ParentToParent = (animal) => animal;
  10. exec(fn2);
  11. // 参数是子类,返回值是父类 (报错)
  12. // 参数、返回值都不安全
  13. type ChildToParent = (blackDog: BlackDog) => Animal;
  14. const fn3:ChildToParent = (blackDog) => animal;
  15. exec(fn3);
  16. // 参数是父类,返回值是子类(正确)
  17. type ParentToChild = (animal: Animal) => BlackDog;
  18. const fn4:ParentToChild = (animal) => blackDog;
  19. exec(fn4);

示例2:

  1. // string|number|boolean 是 string|number 的父类型
  2. // string 是 string|number 的子类型
  3. type Cb = (a:string|number) => string|number;
  4. function exec(cb:Cb):void{}
  5. //报错
  6. type ChildToChild = (a:string) => string;
  7. let fn1:ChildToChild = (a) => 'jack';
  8. exec(fn1);
  9. // 报错
  10. type ChildToParent = (a:string) => string|number|boolean;
  11. let fn2!:ChildToParent;
  12. exec(fn2);
  13. //报错
  14. type ParentToParent = (a:string|number|boolean) => string|number|boolean;
  15. let fn3!:ParentToParent;
  16. exec(fn3);
  17. // ok
  18. type ParentToChild = (a:string|number|boolean) => string;
  19. let fn4!:ParentToChild;
  20. exec(fn4);

泛型的兼容性

泛型在判断兼容性的时候会先判断具体的类型,然后再进行兼容性判断

  1. // 接口内容为空,没用到泛型的时候是可以的。
  2. interface Empty<T>{}
  3. let x!:Empty<string>;
  4. let y!:Empty<number>;
  5. x = y;
  6. // 接口内容不为空的时候不可以
  7. interface NotEmpty<T>{
  8. data: T,
  9. }
  10. let x2!:NotEmpty<string>;
  11. let y2!:NotEmpty<number>;
  12. x2 = y2; //报错
  13. //实现原理如下,称判断具体的类型再判断兼容性
  14. interface NotEmptyString{
  15. data:string
  16. }
  17. interface NotEmptyNumber{
  18. data:number
  19. }
  20. let xx2!:NotEmptyString;
  21. let yy2!:NotEmptyNumber;
  22. xx2 = yy2;

枚举的兼容性

  • 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容
  • 不同枚举类型之间是不兼容的 ```typescript // 数字可以赋给枚举 enum Colors { red, green }; let c:Colors; c = Colors.red; c = 1; //ok c = ‘1’; //报错

// 枚举值可以赋给数字 let n:number; n = 1; n = Colors.red; //ok ```