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

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

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


前言

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

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

文章目录

  • 前言
  • 1、泛型类
  • 2、this指向
    • 箭头函数
    • this参数
  • 3、this类型
  • 4、基于类型守卫的this
  • 5、类表达式
  • 6、抽象类和成员
    • 抽象构造签名
  • 7、类之间的关系
  • 结语

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!");
  • 泛型类的静态成员不能引用类型参数:

    【TypeScript】深入学习TypeScript类(下)_第1张图片

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());

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());

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

  • 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
    

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());

【TypeScript】深入学习TypeScript类(下)_第2张图片

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

  • 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());
    
  • 每个类定义只有一个函数被分配,而不是每个类实例一个函数

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

3、this类型


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

class Box {
    contents: string = "";
    set(value: string) {
        this.contents = value;
        return this;
    }
}

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

【TypeScript】深入学习TypeScript类(下)_第3张图片
创建Box的一个子类:

class ClearableBox extends Box {
    clear() {
        this.contents = "";
    }
}
const a = new ClearableBox(); // a类型为ClearableBox
const b = a.set("hello"); // b类型为ClearableBox
console.log(b);

这里可以看到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));

上面例子中可以看到派生类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));

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);
}

配合接口使用:

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
}

基于 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());
}

5、类表达式


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

const someClass = class<Type> {
    content: Type;
    constructor(value: Type) {
        this.content = value;
    }
};
// type m=someClass
const m = new someClass("Hello, world");

6、抽象类和成员


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

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

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

// 创建一个派生类实现抽象成员
class Derived extends Base {
    getName() {
        return "world";
    }
}
const d = new Derived();
d.printName(); // Hello world

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

抽象构造签名

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

function greet(ctor: typeof Base) {
    const instance = new ctor();
    instance.printName();
}

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

【TypeScript】深入学习TypeScript类(下)_第4张图片
正确的做法应该是使用抽象构造签名

function greet(ctor: new () => Base) {
    const instance = new ctor();
    instance.printName();
}

完整示例:

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);

7、类之间的关系


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

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

    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();
    
  • 空的类通常是其他任何东西的基类:

    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);
    

结语

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

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

参考资料:TypeScript官网

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

你可能感兴趣的:(typescript,学习,javascript)