typescript入门(六)类型检查机制

664 阅读6分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。

一、类型检查机制

类型检查机制:Typescript编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。

类型检查机制的作用:辅助开发,提高开发效率。

类型检查机制分类:

  • 类型推断
  • 类型兼容性
  • 类型保护

二、类型推断

1. 类型推断的概念

类型推断不需要用户指定变量的类型(函数返回值的类型),Typescript可以根据某些规则自动地为其推断出一个类型。

2.类型推断的内容

类型推断包括:

  • 基础类型推断
  • 最佳通用类型推断
  • 上下文类型推断
  1. 基础类型推断
let aa = 1; //number

上例中,ts会自动辨别出aa为number类型。

  1. 最佳通用类型推断
let bb = [1,null]; // number[]

上例中,ts自动推断类型为number[],当tsconfig.js文件中设置了"strictNullChecks": true,时, ts自动推断类型为(number | null)[]

  1. 上下文类型推断

三、类型兼容

当一个类型Y可以被赋值给另一个类型X时,我们就可以说类型X兼容X兼容类型Y。

例:

let s:string = 's';
s = null;

1. 接口兼容性

接口是否兼容可以使用鸭式辨型法。 鸭式辨型法:只要有鸭子的某些特性就是鸭子。 原类型必须具备目标类型的必要属性,成员少的兼容成员多的。

举个例子,

interface X {
    a:any;
}
interface Y {
    a:any;
    b:any;
}
let x1:X = {a:1}
let y1:Y = {a:1,b:2}
x1 = y1

上例中,Y类型拥有X类型中的所有属性方法,X类型兼容Y类型,所以y1可以赋值给x1

2. 函数兼容性

(1)参数个数

函数参数数量只能比类型参数数量少,不能多。

type Handler = (a:number, b:number) => void;
function hof(handler: Handler) {
    return handler;
}

let handler1 = (a:number) => {}
hof(handler1);

let handler2 = (a:number,b:number, c:number)=>{}
hof(handler2);//报错

上例中,handler1的参数个数为1,比Handler类型定义的参数数量少,所以兼容。 handler2的参数个数为2,比Handler类型定义的参数数量多,所以不兼容。

(2)可选参数和剩余参数

固定参数可以兼容可选参数和剩余参数。 可选参数可以兼容固定参数和剩余参数。 剩余参数可以兼容固定参数和可选参数。

let fun11 = (p1:number, p2:number) => {}
let fun22 = (p1?:number, p2?:number) => {}
let fun33 = (...args:number[]) => {}
fun11 = fun22;
fun11 = fun33;

fun22 = fun11;
fun22 = fun33;

fun33 = fun11;
fun33 = fun22;

上例中,fun11是固定参数,fun22包括可选参数,fun3包括剩余参数。

由于固定参数可以兼容可选参数和剩余参数,所以fun11兼容fun22fun33

由于固定参数可以兼容可选参数和剩余参数,所以fun22兼容fun11fun33

由于固定参数可以兼容可选参数和剩余参数,所以fun33兼容fun22fun11

注意:若可选参数可以兼容固定参数和剩余参数,剩余参数可以兼容固定参数和可选参数,出现错误时,修改tsconfig.js中strictFunctionTypesfalse

(3)参数类型

  • 参数的类型必须一致才能兼容。
  • 参数多的兼容参数少的。

例:

type Handler = (a:number, b:number) => void;
function hof(handler: Handler) {
    return handler;
}
let handler3 = (a:string) => {}
hof(handler3); //报错

上例中,handler3的参数a的类型与预定义的类型Handler中的参数类型不一致,所以不兼容。

例:

interface Print3D {
    x:number;
    y:number;
    z:number;
}
interface Print2D {
    x:number;
    y:number;
}

let p3d = (print:Print3D) => {}
let p2d = (print:Print2D) => {}

p3d = p2d
p2d = p3d // 报错

上例中定义了p3d函数参数的类型包含三个参数, p2d函数参数的类型包含两个参数,由于参数多的兼容参数少的,所以p3d兼容p2d,但p2d不兼容p3d

(4)返回值类型 返回值类型相同或为其子类型,则兼容。 例:

let f = ()=>({name: 'Amy'})
let g = ()=>({name: 'Amy',age:12})
f = g
g = f  // 报错

(5)函数重载 对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。

源函数指具体实现。 目标函数指类型定义。

// 目标函数
function overload(a:number,b:number):number;
function overload(a:string,b:string):string;
// 源函数
function overload(a:any,b:any):any{};

3.枚举兼容

枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。

enum Fruit { Apple, Banana }
enum Color { Red, Blue }

let fruit: Fruit.Apple;
let num: number = fruit;

let color: Color.Red = fruit; // 报错

上例中,变量num定义为number类型,能兼容Fruit.Apple类型,但是Color.Red不能兼容Fruit.Apple类型。

4. 类兼容

(1)类中构造函数和静态成员是不进行比较的。

class A {
    constructor(p:number, q:number) { }
    id:number = 1;
}
class B {
    static s = 1;
    constructor() {}
    id:number = 2;
}

let a11 = new A(1,2);
let b11 = new B();
a11 = b11;
b11 = a11;

上例中,类A和B中的构造函数和静态成员不进行比较,两个类中都只有number类型的id属性,所以两个类的实例是兼容的。

(2)若B类中比A类中多属性,则A类的实例兼容B类的实例,但是B类的实例无法兼容A类的实例。

class A {
    constructor(p:number, q:number) { }
    id:number = 1;
}
class B {
    static s = 1;
    constructor() {}
    id:number = 2;
    name: string = '';
}

let a11 = new A(1,2);
let b11 = new B();
a11 = b11;
b11 = a11;//报错

上例中,若B类中比A类中多了个name属性,则A类的实例兼容B类的实例,但是B类的实例无法兼容A类的实例。

(3)如果类中有私有或受保护成员时,则两个无关的类是不兼容的,只有父类和之间可以相互兼容。

class C {
    private id:number = 1;
}
let c11 = new C();
c11 = a11 // 报错
a11 = c11 // 报错

class D extends C {
    name:string = '';
}
let d11 = new D();
c11 = d11;
d11 = c11; // 报错

上例中,类C包含私有属性,类A与C毫无关系,因此两个类的实例不能相互兼容。D类是C类的子类,所以C类的实例兼容D类的实例。但由于D类比C类中多了一个name属性,所以D类的实例不能兼容C类的实例。

5.泛型兼容性

(1)当类型参数没有被内部使用时,不存在兼容性问题。

interface Empty<T> {}

let x: Empty<number>;
let y: Empty<string>;

x = y; // ok

(2)当类型参数被内部成员使用时,会影响兼容性。

interface Empty<T> {
    data: T;
  }
  
  let x: Empty<number>;
  let y: Empty<string>;
  
  x = y; // 报错

(3)对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。

let identity = function<T>(x: T): T { }
let reverse = function<U>(y: U): U { }
identity = reverse;  

6.类型兼容总结

image.png