类型推断

类型检查机制包含: 类型推断 类型兼容性 类型保护

不需要指定变量类型或函数的返回值类型,ts 可自动推断出类型
类型推断包括: 基础类型推断 最佳通用类型推断 上下文类型推断

  1. // 基础类型推断
  2. // 最佳通用类型推断
  3. let a = 1;
  4. let b = [1];
  5. let b1 = [1, null];
  6. let c = (x = 1) => {};
  7. let c1 = (x = 1) => x + 1;
  8. // 上下文推断
  9. // window.onkeydown = (event) => { // KeyboardEvent
  10. // console.log(event.button);
  11. // };

类型断言

  1. interface Foo {
  2. bar: number;
  3. }
  4. let foo = {} as Foo;
  5. foo.bar = 1;

注意: 类型断言可以将某一变量特指为某种类型,但并不会产生接口约束作用,如下举例

  1. interface Foo {
  2. bar: number;
  3. }
  4. let foo = {} as Foo;
  5. // foo.bar = 1 不进行赋值

foo 被指定为 Foo 接口类型,但真实的 foo 是一个空对象,没有 foo.bar =1相当于没有 bar 属性,所以此时的接口并不能起到约束作用。 解决: 直接在声明的时将对象指定为接口类型,而不使用类型断言

  1. {
  2. interface Foo {
  3. bar: number;
  4. }
  5. let foo: Foo = {};
  6. }
  7. {
  8. interface Foo {
  9. bar: number;
  10. }
  11. // let foo = {} as Foo;
  12. // foo.bar = 1;
  13. let foo: Foo = {
  14. bar: 1,
  15. };
  16. }

image.png

各种类型之间的兼容性

tsconfig.json => “strictNullChecks”: false,(个人不推荐)

  1. /**
  2. * X 兼容 Y: X (目标类型) = Y (源类型)
  3. */
  4. let s: string = "a";
  5. s = null;

接口兼容性

接口类型相当于对变量属性的约束,必须全部满足才可以兼容赋值

  1. // 接口兼容性
  2. interface x {
  3. a: any;
  4. b: any;
  5. }
  6. interface Y {
  7. a: any;
  8. b: any;
  9. c: any;
  10. }
  11. let x: X = { a: 1, b: 2 };
  12. let y: Y = { a: a, b: 2, c: 3 };
  13. // 将变量y赋值给变量x, 测试接口X是否兼容接口Y
  14. x = y; // 源类型Y具备目标类型X的全部必要属性
  15. // 将变量x赋值给变量y, 测试接口Y是否兼容接口X
  16. y = x; // 源类型X不具备目标类型Y的全部必要属性(不含c属性)

image.png

函数的兼容性

目标函数能够兼容源函数,需同时满足以下三个条件: 参数的个数(目标函数的参数个数一定要多于源函数的参数个数) 参数的类型(参数类型必须匹配) 返回值的类型(标函数的返回值类型 与 原函数的返回值类型 必须相同或为其子类型)

  1. // 函数兼容性
  2. type Handler = (a: number, b: number) => void;
  3. function hof(hadler: Handler) {
  4. return hadler;
  5. }

函数兼容性的影响因素

参数个数

  1. let hadler1 = (a: number) => {}; // 少参数
  2. hof(hadler1);
  3. let hadler2 = (a: number, b: number, c: number) => {}; // 多参数
  4. hof(hadler2);
  5. // 当参数不固定时,固定参数与可选参数,剩余参数的兼容性
  6. {
  7. // 可选参数和剩余参数
  8. let a = (p1: number, p2: number) => {};
  9. let b = (p1?: number, p2?: number) => {};
  10. let c = (...args: number[]) => {};
  11. // tsconfig.json -> "strictFunctionTypes": false,
  12. a = b; // 固定参数可以兼容可选参数
  13. a = c; // 固定参数可以兼容剩余参数
  14. // tsconfig.json ("strictFunctionTypes": false可兼容)
  15. // b = c; // 可选参数不兼容固定参数
  16. // b = a; // 可选参数不兼容剩余参数
  17. c = a; // 剩余参数不兼容固定参数
  18. c = b; // 可选参数不兼容剩余参数
  19. }

image.png

参数类型

基础类型
  1. // 函数兼容性
  2. type Handler = (a: number, b: number) => void;
  3. function hof(hadler: Handler) {
  4. return hadler;
  5. }
  6. let hadler3 = (a: number) => {};
  7. hof(hadler3);
  8. // let hadler3 = (a: string) => { }
  9. // hof(hadler3)

image.png

对象类型
  1. interface Point3D {
  2. x: number;
  3. y: number;
  4. z: number;
  5. }
  6. interface Point2D {
  7. x: number;
  8. y: number;
  9. }
  10. let p2d = (ponit: Point2D) => {};
  11. let p3d = (ponit: Point3D) => {};
  12. // tsconfig.json ("strictFunctionTypes": false可兼容)
  13. p2d = p3d // Point2D 不兼容 Point3D
  14. p3d = p2d; // Point3D 兼容 Point2D

image.png

返回值类型

  1. let f1 = () => ({ name: "Alice" });
  2. let g = () => ({ name: "Alice", location: "Beijing" });
  3. // 成员少的兼容成员多的
  4. f1 = g;
  5. g = f1;

image.png

小技巧: 返回值类型,和接口类似相当于是对函数的一种约束,所以必须在满足要求的前提先才可以兼容 即参数可以多了我不用,但绝对不可以少

函数重载对函数兼容性的影响

  1. // 函数定义-重载列表
  2. function overload(a: number, b: number): number;
  3. function overload(a: number, b: number): string;
  4. // 函数实现
  5. // 兼容: 目标函数参数个数 > 源函数参数个数
  6. function overload(a: any, b: any): any {}
  7. // 不兼容: 目标函数参数个数 < 源函数参数个数
  8. // function overload(a: any, b: any, c: any): any {}
  9. // 不兼容: 删除返回值
  10. // function overload(a: any, b: any) {}

枚举类型的兼容性

  1. // 枚举兼容性
  2. enum Fruit {
  3. Apple,
  4. Banana,
  5. }
  6. enum Color {
  7. Red,
  8. Yellow,
  9. }
  10. let fruit: Fruit.Apple = 1;
  11. let no: number = Fruit.Apple;
  12. // 不同枚举之间不兼容
  13. // let color: Color.Red = Fruit.Apple;

image.png

类的兼容性

  1. // 类的兼容性
  2. // 静态成员和构造函数不参与类的兼容性比较
  3. class A {
  4. constructor(p: number, q: number) {} // 构造函数
  5. id: number = 1;
  6. private name: string = ""; // 私有成员
  7. }
  8. class B {
  9. static s: number = 1; // 静态成员
  10. constructor(p: number) {} // 构造函数
  11. id: number = 2;
  12. private name: string = ""; // 私有成员
  13. }
  14. let aa = new A(1, 2);
  15. let bb = new B(1);
  16. // 类中包含私有成员,两个类不兼容
  17. // aa = bb;
  18. // bb = aa;
  19. // 但父类和子类之间兼容
  20. class C1 extends A {} // 创建子类
  21. let cc = new C1(1, 2);
  22. aa = cc; // 父类兼容子类
  23. cc = aa; // 子类兼容父类

泛型的兼容性

  1. // 当类型参数T未被成员使用是,不会影响泛型兼容性
  2. interface Empty<T> {}
  3. let obj1: Empty<number> = {};
  4. let obj2: Empty<string> = {};
  5. obj1 = obj2;
  6. // 当类型参数T被成员使用时,才会影响泛型兼容性
  7. interface Empty1<T> {
  8. attr: T;
  9. }
  10. let obj3: Empty1<number> = { attr: 1 };
  11. let obj4: Empty1<string> = { attr: "1" };
  12. // obj3 = obj4;
  13. // 泛型函数的兼容性
  14. // 定义相同的两个泛型函数,如果未指定具体类型,则可相互兼容
  15. let t1 = <T>(x: T): T => {
  16. console.log("x");
  17. return x;
  18. };
  19. let t2 = <U>(y: U): U => {
  20. console.log("y");
  21. return y;
  22. };
  23. t1 = t2; // 未指定类型的两个定义相同的泛型函数,可相互兼容

结构之间兼容:成员少的兼容成员多的 函数之间兼容:参数多的兼容参数少的

类型保护机制

  1. enum Type {
  2. Strong,
  3. Week,
  4. }
  5. class Java {
  6. helloJava() {
  7. console.log("Hello Java");
  8. }
  9. java: any; // 新增java属性
  10. }
  11. class JavaScript {
  12. helloJavaScript() {
  13. console.log("Hello JavaScript");
  14. }
  15. javascript: any; // 新增javascript属性
  16. }
  17. function getLanguage(type: Type) {
  18. let lang = type === Type.Strong ? new Java() : new JavaScript();
  19. return lang;
  20. }
  21. getLanguage(Type.Strong);

TS不能准确判断实例到底是哪一种类型,因为TS不能准确判断实例到底是哪一种类型
image.png
使用类型断言

  1. function getLanguage(type: Type) {
  2. let lang = type === Type.Strong ? new Java() : new JavaScript();
  3. if ((lang as Java).helloJava) {
  4. (lang as Java).helloJava();
  5. } else {
  6. (lang as JavaScript).helloJavaScript();
  7. }
  8. return lang;
  9. }

创建类型保护区块

使用instance关键字创建区块

  1. function getLanguage(type: Type, x: string | number) {
  2. let lang = type === Type.Strong ? new Java() : new JavaScript();
  3. if (lang instanceof Java) {
  4. lang.helloJava();
  5. } else {
  6. lang.helloJavaScript();
  7. }
  8. return lang;
  9. }

通过in关键字判断变量所属类型来创建区块

  1. function getLanguage(type: Type, x: string | number) {
  2. let lang = type === Type.Strong ? new Java() : new JavaScript();
  3. if ("java" in lang) {
  4. lang.helloJava();
  5. } else {
  6. lang.helloJavaScript();
  7. }
  8. return lang;
  9. }

通过typeof对参数x的类型进行判断,创建对应的区块

  1. function getLanguage(type: Type, x: string | number) {
  2. let lang = type === Type.Strong ? new Java() : new JavaScript();
  3. if (typeof x === "string") {
  4. x.length; // string
  5. } else {
  6. x.toFixed(2); // number
  7. }
  8. return lang;
  9. }

使用类型保护函数

  1. function getLanguage(type: Type, x: string | number) {
  2. let lang = type === Type.Strong ? new Java() : new JavaScript();
  3. if (isJava(lang)) {
  4. lang.helloJava;
  5. } else {
  6. lang.helloJavaScript;
  7. }
  8. return lang;
  9. }

学习笔记出自于梁宵老师课程