返回介绍

Class

发布于 2024-09-11 00:55:47 字数 5190 浏览 0 评论 0 收藏 0

类与类成员的类型签名

类的主要结构有构造函数、属性、方法和访问符(Accessor)。

属性的类型标注类似于变量,而构造函数、方法、存取器的类型编标注类似于函数:

class Foo {
  prop: string;

  constructor(inputProps: string) {
    this.prop = inputProps;
  }

  print (addon: string): string {
    return `${this.prop} and ${addon}`;
  }

  get PropA(): string {
    return `${this.prop}+A`;
  }

  set PropA(value: string) {
    this.prop = `${value}`;
  }
}

setter 方法不允许进行返回值的类型标注,可以理解为 setter 的返回值并不会被消费,它是一个只关注过程的函数。类的方法同样可以进行函数那样的重载。

类也可以通过类声明和类表达式的方式创建。上面的写法即是类声明,而使用类表达式的语法则是这样的:

const Foo = class {
  prop: string;

  constructor(inputProps: string) {
    this.prop = inputProps;
  }

  print (addon: string): string {
    return `${this.prop} and ${addon}`;
  }

  get PropA(): string {
    return `${this.prop}+A`;
  }

  set PropA(value: string) {
    this.prop = `${value}`;
  }
}

修饰符

在 TypeScript 中能够为 Class 成员添加这些修饰符:public / private / protected / readonly。

除 readonly 以外,其他三位都属于访问性修饰符,而 readonly 属于操作性修饰符(就和 interface 中的 readonly 意义一致)。

这些修饰符应用的位置在成员命名前:

class Foo {
  private prop: string;

  constructor(inputProps: string) {
    this.prop = inputProps;
  }

  protected print (addon: string): string {
    return `${this.prop} and ${addon}`;
  }

  public get PropA(): string {
    return `${this.prop}+A`;
  }

  public set PropA(value: string) {
    this.prop = `${value}`;
  }
}

通常不会为构造函数添加修饰符,而是让它保持默认的 public。

  • public:此类成员在类、类的实例、子类中都能被访问。
  • private:此类成员仅能在类的内部被访问。
  • protected:此类成员仅能在类与子类中被访问。

当你不显式使用访问性修饰符,成员的访问性默认会被标记为 public。简单起见,可以在构造函数中对参数应用访问性修饰符:

class Foo {
  constructor(public arg1: string, private arg2: boolean) {
  }
}

new Foo('wangxiaobai', false);

此时,参数会被直接作为类的成员(即实例的属性),免去后续的手动赋值。

静态成员

在 TypeScript 中,可以使用 static 关键字来标识一个成员为静态成员:

class Foo {
  static staticHandler () {}
  public instanceHandler () {}
}

不同于实例成员,在类的内部静态成员无法通过 this 来访问,需要通过 Foo.staticHandler 这种形式进行访问。

可以查看编译到 ES5 及以下 target 的 JavaScript 代码(ES6 以上就原生支持静态成员了),来进一步了解它们的区别:

var Foo = /** @class */ (function () {
    function Foo() {
    }
    Foo.staticHandler = function () { };
    Foo.prototype.instanceHandler = function () { };
        return Foo;
    }());

从中可以看到,静态成员直接被挂载在函数体上,而实例成员挂载在原型上,这就是二者的最重要差异:静态成员不会被实例继承,它始终只属于当前定义的这个类(以及其子类)。而原型对象上的实例成员则会沿着原型链进行传递,也就是能够被继承。

而对于静态成员和实例成员的使用时机,其实并不需要非常刻意地划分。比如用类 + 静态成员来收敛变量与 utils 方法:

class Utils {
  static identifier = 'wangxiaobai'

  static studyWithU () {

  }

  static makeUHappy () {
    Utils.studyWithU()
  }
}

Utils.makeUHappy()

继承、实现、抽象类

说到 Class,那一定离不开继承。TypeScript 中也使用 extends 关键字来实现继承:

class Base {}

class Derived extends Base {}

对于这里的两个类,比较严谨的称呼是基类(Base)与派生类(Derived)。当然,如果叫父类与子类也没问题。关于基类与派生类,需要了解的主要是派生类对基类成员的访问与覆盖操作。

基类中的哪些成员能够被派生类访问,完全是由其访问性修饰符决定的。派生类中可以访问到使用 public 或 protected 修饰符的基类成员。

除了访问以外,基类中的方法也可以在派生类中被覆盖,但仍然可以通过 super 访问到基类中的方法:

class Base {
  print () {}
}

class Derived extends Base {
  print() {
    super.print();
    // ...
  }
}

在派生类中覆盖基类方法时,并不能确保派生类的这一方法能覆盖基类方法,万一基类中不存在这个方法呢?

所以,TypeScript 4.3 新增了 override 关键字,来确保派生类尝试覆盖的方法一定在基类中存在定义:

class Base {
  printWithLove() {}
}

class Derived extends Base {
  override print() {
    super.print();
  }
}

在这里 TypeScript 将会给出错误,因为尝试覆盖的方法并未在基类中声明。通过这一关键字就能确保首先这个方法在基类中存在,同时标识这个方法在派生类中被覆盖了。

除了基类与派生类以外,还有一个比较重要的概念:抽象类。

抽象类是对类结构与方法的抽象,简单来说,一个抽象类描述了一个类中应当有哪些成员(属性、方法等),一个抽象方法描述了这一方法在实际实现中的结构,抽象方法其实描述的就是这个方法的入参类型与返回值类型。

抽象类使用 abstract 关键字声明:

abstract class AbsFoo {
  abstract absProp: string;
  abstract get absGetter (): string;
  abstract absMethod (name: string): string;
}

注意,抽象类中的成员也需要使用 abstract 关键字才能被视为抽象类成员,如这里的抽象方法。

实现(implements)一个抽象类:

class Foo implements AbsFoo {
  absProp: string = 'wangxiaobai';

  get absGetter(): string {
    return 'wangxiaobai';
  }

  absMethod(name: string): string {
    return name;
  }
}

此时,必须完全实现这个抽象类的每一个抽象成员。需要注意的是,在 TypeScript 中无法声明静态的抽象成员。

对于抽象类,它的本质就是描述类的结构。interface 不仅可以声明函数结构,也可以声明类的结构:

interface IFooStruct {
  absProp: string;
  get absGetter (): string;
  absMethod (name: string): string;
}

class Foo implements IFooStruct {
  absProp: string = 'wangxiaobai';

  get absGetter(): string {
    return 'wangxiaobai';
  }

  absMethod(name: string): string {
    return name;
  }
}

在这里让类去实现了一个接口。这里接口的作用和抽象类一样,都是描述这个类的结构。除此以外,还可以使用 Newable Interface 来描述一个类的结构(类似于描述函数结构的 Callable Interface):

class Foo { }

interface FooStruct {
    new(): Foo
}

declare const NewableFoo: FooStruct;

const foo = new NewableFoo();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文