• 【TypeScript】深入学习TypeScript类(下)


    🚀 TypeScript学习TypeScript从入门到精通
    🚀 TypeScript类(上篇)TypeScript类(上篇)
    🚀 蓝桥杯真题解析蓝桥杯Web国赛真题解析

    🚀 个人简介:即将大三的学生,热爱前端,热爱生活🍬
    🚀 你的一键三连是我更新的最大动力❤️!


    🍂 前言

    最近博主一直在创作TypeScript的内容,所有的TypeScript文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅❤️

    本篇文章将继续去讲解TypeScript中的class类(由于内容较多,将其分为了上下两篇,查看上篇内容请点击这里),这也许会是你看过的最全面最细致的TypeScript教程,点赞关注收藏不迷路🚀🚀🚀!

    1、泛型类


    类和接口一样,可以是泛型的,当一个泛型类用new实例化时,其类型参数的推断方式与函数调用的方式相同:

    class Box<Type> {
        contents: Type;
        constructor(value: Type) {
            this.contents = value;
        }
    }
    // const b: Box
    const b = new Box("hello!");
    // 等同于const b = new Box("hello!");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 泛型类的静态成员不能引用类型参数:

      在这里插入图片描述

    2、this指向


    JavaScriptthis指向是一个头疼的问题,默认情况下函数内this的值取决于函数的调用方式,在一些情况下这会出现意向不到的效果,如下方代码:

    class MyClass {
        name = "MyClass";
        getName() {
            return this.name;
        }
    }
    const c = new MyClass();
    const obj = {
        name: "obj",
        getName: c.getName,
    };
    // 输出 "obj", 而不是 "MyClass"
    console.log(obj.getName());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    TypeScript提供了一些方法来减少或防止这种错误:

    🍂 箭头函数

    class MyClass {
        name = "MyClass";
        getName = () => {
            return this.name;
        };
    }
    const c = new MyClass();
    const obj = {
        name: "obj",
        getName: c.getName,
    };
    // 输出 "MyClass", 而不是 "obj"
    console.log(obj.getName());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用箭头函数也是有一些妥协的:

    • this 值保证在运行时是正确的,即使是没有经过TypeScript检查的代码也是如此

    • 这将使用更多的内存,因为每个类实例将有它自己的副本,每个函数都是这样定义的

    • 你不能在派生类中使用super 调用基类方法,因为在原型链中没有入口可以获取基类方法:

      class MyClass {
          name = "MyClass";
          getName = () => {
              return this.name;
          };
      }
      class A extends MyClass {
          AName: string;
          constructor() {
              super();
              // getName为箭头函数时,调用super.getName()会报错
              // this.AName = super.getName();
              this.AName = this.getName(); // 但一直能通过this.getName()调用
          }
      }
      const a = new A();
      console.log(a.AName); // MyClass
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

    🍂 this参数

    【TypeScript】深入学习TypeScript函数中我们提到过this参数,TypeScript检查调用带有this 参数的函数,是否在正确的上下文中进行

    我们可以不使用箭头函数,而是在方法定义中添加一个this 参数,以静态地确保方法被正确调用:

    class MyClass {
        name = "MyClass";
        getName(this: MyClass) {
            return this.name;
        }
    }
    const c = new MyClass();
    // 正确
    c.getName();
    // 错误
    const g = c.getName;
    console.log(g());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    这种方法做出了与箭头函数方法相反的取舍:

    • JavaScript调用者仍然可能在不知不觉中错误地使用类方法,如上面的例子:

      class MyClass {
          name = "MyClass";
          getName(this: MyClass) {
              return this.name;
          }
      }
      const c = new MyClass();
      const obj = {
          name: "obj",
          getName: c.getName,
      };
      // 依旧输出 "obj", 而不是 "MyClass"
      console.log(obj.getName());
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 每个类定义只有一个函数被分配,而不是每个类实例一个函数

    • 基类方法定义仍然可以通过 super 调用。

    3、this类型


    在类中,一个叫做 this 的特殊类型动态地指向当前类的类型,看下面的这个例子:

    class Box {
        contents: string = "";
        set(value: string) {
            this.contents = value;
            return this;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里,TypeScript推断出 set 方法的返回类型是this ,而不是Box

    在这里插入图片描述
    创建Box的一个子类:

    class ClearableBox extends Box {
        clear() {
            this.contents = "";
        }
    }
    const a = new ClearableBox(); // a类型为ClearableBox
    const b = a.set("hello"); // b类型为ClearableBox
    console.log(b);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里可以看到b的类型竟然是ClearableBox,这说明此时set方法返回的this类型指向了当前的类ClearableBox(因为是在ClearableBox上调用的set

    可以在参数类型注释中使用this

    class Box {
        contents: string = "";
        // 类型注释中使用this
        sameAs(other: this) {
            return other.contents === this.contents;
        }
    }
    class ClearableBox extends Box {
        contents: string = "Ailjx";
    }
    class B {
        contents: string = "";
    }
    
    const box = new Box();
    const clearableBox = new ClearableBox();
    const b = new B();
    
    console.log(clearableBox.sameAs(box)); // false
    
    // ❌❌❌报错
    // 类型“B”的参数不能赋给类型“ClearableBox”的参数
    // 类型 "B" 中缺少属性 "sameAs",但类型 "ClearableBox" 中需要该属性
    console.log(clearableBox.sameAs(b));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    上面例子中可以看到派生类ClearableBoxsameAs 方法能够接收基类的实例

    但是当派生类中有额外的属性后,它就只能接收该同一派生类的其它实例了:

    class Box {
        contents: string = "";
        sameAs(other: this) {
            return other.contents === this.contents;
        }
    }
    class ClearableBox extends Box {
        otherContents: string = "Ailjx";
    }
    
    const box = new Box();
    const clearableBox = new ClearableBox();
    
    // ❌❌❌报错:
    // 类型“Box”的参数不能赋给类型“ClearableBox”的参数。
    // 类型 "Box" 中缺少属性 "otherContents",但类型 "ClearableBox" 中需要该属性。
    console.log(clearableBox.sameAs(box));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4、基于类型守卫的this


    我们可以在类和接口的方法的返回位置使用类型谓词this is Type,当与类型缩小混合时(例如if语句),目标对象的的类型将被缩小到指定的Type

    类型谓词详见【TypeScript】TypeScript中类型缩小(含类型保护)与类型谓词

    class Box {
        // 利用类型谓词,当this类型是A的实例时,确保将this类型缩小为A类型
        isA(): this is A {
            return this instanceof A;
        }
        isB(): this is B {
            return this instanceof B;
        }
    }
    
    class A extends Box {
        Apath: string = "A";
    }
    class B extends Box {
        Bpath: string = "B";
    }
    
    // fso的类型为基类Box,它可能是A,也可能是B
    const fso: Box = Math.random() > 0.5 ? new A() : new B();
    
    if (fso.isA()) {
        // fso.isA()为true时(说明Box的this类型指向了A,即可知道此时fso具体为A),
        // 其通过类型谓词将fso缩小为了A类型,此时就可以安全调用A特有的属性
        console.log(fso.Apath);
    } else if (fso.isB()) {
        console.log(fso.Bpath);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    配合接口使用:

    class Box {
        isNetworked(): this is Networked & this {
            return this.networked;
        }
        // networked属性控制Box是否包含Networked接口类型
        constructor(private networked: boolean) {} // 这里使用了在构造器参数列表中声明属性
    }
    interface Networked {
        host: string;
    }
    
    const A: Box = new Box(true);
    
    // A.host = "12"; // ❌❌外界直接使用host属性报错:类型“Box”上不存在属性“host”
    
    if (A.isNetworked()) {
        // 此时A类型变成了Networked & this,可以安全使用host属性了
        A.host = "12";
        console.log(A.host); // 12
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    基于 this 的类型保护的一个常见用例,是允许对一个特定字段进行懒惰验证。例如,这种情况下,当hasValue 被验证为真时,Box类型缩小,value属性失去了可选性,就能直接使用了:

    class Box<T> {
        value?: T;
        // 根据value值是否存在来缩小类型
        hasValue(): this is { value: T } {
            return this.value !== undefined;
        }
    }
    
    const box = new Box<string>();
    
    // value可能未定义需要使用可选连?
    console.log(box.value?.toUpperCase());
    
    if (box.hasValue()) {
        // 这时Box类型已经缩小为{value:string}了,value不再是可选属性了,可以不使用可选连?了
        console.log(box.value.toUpperCase());
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5、类表达式


    类表达式与类声明非常相似,唯一真正的区别是,类表达式不需要一个名字,我们可以通过它们最终绑定的任何标识符来引用它们:

    const someClass = class<Type> {
        content: Type;
        constructor(value: Type) {
            this.content = value;
        }
    };
    // type m=someClass
    const m = new someClass("Hello, world");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6、抽象类和成员


    使用abstract 定义的一个方法或字段称为抽象成员,它是一个没有提供实现的方法或字段,这些成员必须存在于一个使用abstract 定义的抽象类中,该类不能直接实例化

    abstract class Base {
        abstract getName(): string;
        printName() {
            console.log("Hello, " + this.getName());
        }
    }
    // ❌❌❌报错:无法创建抽象类的实例
    const b = new Base();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    抽象类的作用是作为子类的基类实现所有的抽象成员

    // 创建一个派生类实现抽象成员
    class Derived extends Base {
        getName() {
            return "world";
        }
    }
    const d = new Derived();
    d.printName(); // Hello world
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果抽象类的派生类不实现它的抽象成员则会报错:
    在这里插入图片描述

    🍂 抽象构造签名

    向上面这个例子,如果你想要写一个函数,能够接受所有抽象类Base的派生类,你可能会这样写:

    function greet(ctor: typeof Base) {
        const instance = new ctor();
        instance.printName();
    }
    
    • 1
    • 2
    • 3
    • 4

    这时TypeScript会告诉你这样写是不对的:

    在这里插入图片描述
    正确的做法应该是使用抽象构造签名

    function greet(ctor: new () => Base) {
        const instance = new ctor();
        instance.printName();
    }
    
    • 1
    • 2
    • 3
    • 4

    完整示例:

    abstract class Base {
        abstract getName(): string;
        printName() {
            console.log("Hello, " + this.getName());
        }
    }
    
    class Derived extends Base {
        getName() {
            return "world";
        }
    }
    class Derived2 extends Base {
        getName() {
            return "world2";
        }
    }
    
    function greet(ctor: new () => Base) {
        const instance = new ctor();
        instance.printName();
    }
    
    greet(Derived);
    greet(Derived2);
    
    // ❌❌❌报错:类型“typeof Base”的参数不能赋给类型“new () => Base”的参数。
    //   无法将抽象构造函数类型分配给非抽象构造函数类型。
    greet(Base);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    7、类之间的关系


    • 相同的类可以互相替代使用:

      class Point1 {
          x = 0;
          y = 0;
      }
      class Point2 {
          x = 0;
          y = 0;
      }
      // 正确
      const p: Point1 = new Point2();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 即使没有明确的继承,类之间的子类型关系也是存在的:

      class Person {
          name: string = "A";
          age: number = 1;
      }
      class Employee {
          name: string = "A";
          age: number = 1;
          salary: number = 99;
      }
      // type A = number
      type A = Employee extends Person ? number : string;
      // 正确
      const p: Person = new Employee();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 空的类通常是其他任何东西的基类:

      class Person {
          name: string = "A";
          age: number = 1;
      }
      class Employee {
          salary: number = 99;
      }
      class N {}
      // type A = number
      type A = Person extends N ? number : string;
      // type B = number
      type B = Employee extends N ? number : string;
      
      function fn(x: N) {}
      // 以下调用均可
      fn(Person);
      fn(Employee);
      fn(window);
      fn({});
      fn(fn);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

    🍂 结语

    至此,TypeScript类的内容就全部结束了,关注博主下篇更精彩!

    博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。

    参考资料:TypeScript官网

    如果本篇文章对你有所帮助,还请客官一件四连!❤️

  • 相关阅读:
    QT实现可拖动自定义控件
    解决HtmlUnit执行JS报错提示ScriptException
    Prometheus----3
    大厂真题:【位运算】米哈游2023秋招-相加异或
    win11系统如何访问ie浏览器(不用额外安装IE浏览器,使用win11系统自带功能即可访问ie浏览器)
    微服务中的熔断、降级和限流
    2022年第十一届认证杯数学中国数学建模国际赛小美赛:C 题 对人类活动进行分类 建模方案及代码实现
    pandas read_json时ValueError: Expected object or value的解决方案
    [经典力扣面试题]135. 分发糖果
    Kafka 核心源码解读【三】--Controller模块
  • 原文地址:https://blog.csdn.net/m0_51969330/article/details/126091018