8.结构类型系统

272 阅读4分钟

8.结构类型系统

8.1 接口的兼容性

  • 如果传入的变量和声明的类型不匹配,TS就会进行兼容性检查

  • 原理是Duck-Check,就是说只要目标类型中声明的属性变量在源类型中都存在就是兼容的


interface Animal{

name:string;

age:number

}

interface Person{

name:string;

age:number;

gender:number

}

function getName(a:Animal):string{

return a.name;

}

let a: Animal = {

name:'',

age:10

}

getName(a);

let p: Person = {

name: "",

age: 10,

gender:0

};

getName(p);

8.2 基本类型的兼容性


let num : string|number;

let str:string='zhufeng';

num = str;

let num2 : {

toString():string

}

let str2:string='jiagou';

num2 = str2;

//str2 = num2; //Type '{ toString(): string; }' is not assignable to type 'string'

8.3 类的兼容性

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

namespace ab{

class Animal {name:string}

class Bird extends Animal {age:number}

let a: Animal;

let b: Bird;

a=new Bird();

//b=new Animal(); //Property 'age' is missing in type 'Animal' but required in type 'Bird'.

}

namespace cd{

//甚至没有关系的两个类的实例也是可以的

class Animal{

name:string

}

class Bird{

name:string

}

let a:Animal ;

a = new Bird();

let b:Bird;

b = new Animal();

}

8.4 函数的兼容性

  • 比较函数的时候是要先比较函数的参数,再比较函数的返回值

8.4.1 比较参数


type sumFunc = (a:number,b:number)=>number;

let sum:sumFunc;

function f1(a:number,b:number):number{

return a+b;

}

sum = f1;

//可以省略一个参数

function f2(a:number):number{

return a;

}

sum = f2;

//可以省略二个参数

function f3():number{

return 0;

}

sum = f3;

//多一个参数可不行

function f4(a:number,b:number,c:number){

return a+b+c;

}

sum = f4;

8.4.2 比较返回值


let getPerson:GetPerson;

//返回值一样可以

function g1(){

return {name:'zhufeng',age:10};

}

getPerson = g1;

//返回值多一个属性也可以

function g2(){

return {name:'zhufeng',age:10,gender:'male'};

}

getPerson = g2;

//返回值少一个属性可不行

function g3(){

return {name:'zhufeng'};

}

getPerson = g3;

//因为有可能要调用返回值上的方法

getPerson().age.toFixed();

8.5 函数的协变与逆变

  • 协变(Covariant):只在同一个方向;

  • 逆变(Contravariant):只在相反的方向;

  • 双向协变(Bivariant):包括同一个方向和不同方向;

  • 不变(Invariant):如果类型不完全相同,则它们是不兼容的。

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

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

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

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

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

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


/**

* 参数可以传自己和自己的父类

* 返回值可以传自己和自己的子类

* 四种情况

* 1.参数传子类返回值子类 n

* 2.参数是子类返回值是父类 n

* 3.参数是父类返回值是父类 n

* 4.参数是父类返值是子类 y

*/

class Animal{}

class Dog extends Animal{

public name:string = 'Dog'

}

class BlackDog extends Dog {

public age: number = 10

}

class WhiteDog extends Dog {

public home: string = '北京'

}

let animal: Animal;

let blackDog: BlackDog;

let whiteDog: WhiteDog;

type Callback = (dog: Dog)=>Dog;

function exec(callback:Callback):void{

callback(whiteDog);

}

//不行 callback(redDog);

type ChildToChild = (blackDog: BlackDog) => BlackDog;

const childToChild: ChildToChild = (blackDog: BlackDog): BlackDog => blackDog

exec(childToChild);

//也不行,理由同上

type ChildToParent = (blackDog: BlackDog) => Animal;

const childToParent: ChildToParent = (blackDog: BlackDog): Animal => animal

exec(childToParent);

//不行 因为有可能调用返回的Dog的方法

type ParentToParent = (animal: Animal) => Animal;

const parentToParent: ParentToParent = (animal: Animal): Animal => animal

exec(parentToParent);

//可以,所有的狗都是动物,返回的不管什么狗都是狗

type ParentToChild = (animal: Animal) => BlackDog;

const parentToChild: ParentToChild = (animal: Animal): BlackDog => blackDog

exec(parentToChild);

//(Animal → Greyhound) ≼ (Dog → Dog)

//返回值类型很容易理解:黑狗是狗的子类。但参数类型则是相反的:动物是狗的父类!


type Callback = (a:string|number)=>string|number;

function exec(callback:Callback){

callback('');

}

type ChildToChild = (a: string) => string;

let childToChild: ChildToChild;

//exec(childToChild);//n

type ChildToParent = (a: string) => string|number|boolean;

let childToParent: ChildToParent;

//exec(childToParent);//n

type ParentToParent = (a: string|number|boolean) => string|number|boolean;

let parentToParent: ParentToParent;

//exec(parentToParent);//n

type ParentToChild = (a: string|number|boolean) => string;

let parentToChild: ParentToChild;

exec(parentToChild);//y

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

8.6 泛型的兼容性

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

//接口内容为空没用到泛型的时候是可以的

//1.接口内容为空没用到泛型的时候是可以的

interface Empty<T>{}

let x!:Empty<string>;

let y!:Empty<number>;

x = y;

//2.接口内容不为空的时候不可以

interface NotEmpty<T>{

data:T

}

let x1!:NotEmpty<string>;

let y1!:NotEmpty<number>;

x1 = y1;

//实现原理如下,称判断具体的类型再判断兼容性

interface NotEmptyString{

data:string

}

interface NotEmptyNumber{

data:number

}

let xx2!:NotEmptyString;

let yy2!:NotEmptyNumber;

xx2 = yy2;

8.7 枚举的兼容性

  • 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容

  • 不同枚举类型之间是不兼容的


//数字可以赋给枚举

enum Colors {Red,Yellow}

let c:Colors;

c = Colors.Red;

c = 1;

c = '1';

//枚举值可以赋给数字

let n:number;

n = 1;

n = Colors.Red;