先介绍一下类的一些基本概念:

  • 类:定义了一件事物的抽象特点,包含它的属性和方法。
  • 对象:类的实例,通过 new 生成。
  • 面向对象(OOP)的三大特性:封装、继承、多态。
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据。
  • 继承(Inhetitance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性。
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 CatDog 都继承自 Animal,但是分别实现了自己的 eat 方法,此时针对某一个实例,我们无需了解它是 Cat 还是 Dog ,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
  • 存取器:用以改变属性的读取和赋值行为。
  • 修饰符:修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性和方法。
  • 抽象类:抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现。
  • 接口:不同类之间共有的属性和方法,可以抽象成一个接口,接口可以被类实现。一个类只能继承自另一个类,但是可以实现多个接口。

ES6 开始, JavaScript 就支持了基于类的面向对象的方式。因此 TypeScript 也支持了这一方式。

下面来看看例子:

  1. class Greeter {
  2. greeting: string;
  3. constructor(message: string) {
  4. this.greeting = message;
  5. }
  6. greet () {
  7. return "hello, " + this.greeting;
  8. }
  9. }
  10. let greeter = new Greeter("world");

在例子中,声明了一个 Greeter 的类。这个类有 3 个成员:一个叫做 greeting 的属性,一个构造函数和一个 greet 方法。

类的继承

基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
在 typescript 中使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
派生类通常被称作子类,基类通常被称作超类。

派生类包含了一个构造函数,它必须调用 super() ,它会执行基类的构造函数。这个是 typescript 强制执行的一条重要规则。

  1. class Person {
  2. name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. run():string {
  7. return `${this.name} is running.`
  8. }
  9. }
  10. class Web extends Person {
  11. constructor(name: string) {
  12. super(name);
  13. }
  14. }

如果父类和子类有同样的方法,使用子类的方法。

修饰符

public

public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public

private

private 修饰的属性或方法是私有的,不能在声明它的类的外部访问,在子类中也是不允许访问的。当构造函数修饰为 private 时,该类不允许被继承或者实例化。

需要注意的是:当比较带有 privateprotected 成员的类型的时候。如果其中一个类型里包含一个 private 成员,那么只有当另外一个类型中也存在这样一个 private 成员,并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。对于 protected 成员也适用这个规则。

protected

protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中是允许被访问的。当构造函数修饰为 protected 时,该类只允许被继承。

readonly

只读属性关键字。只读属性必须在声明时或构造函数里被初始化。

注意:如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。

参数属性

修饰符还可以使用在构造函数参数中,等同于类中定义该属性,使代码更简洁。这时称这个属性为参数属性。

参数属性通过给构造函数参数前面添加一个访问限定符来声明。使用 private 限定一个参数属性会声明并初始化一个私有成员;对于 publicprotected 来说也是一样。

看个例子:

  1. // 没使用参数属性的时候
  2. class Person {
  3. readonly name: string;
  4. constructor (theName: string) {
  5. this.name = theName;
  6. }
  7. }
  8. // 使用了参数属性,等同于上面的代码
  9. class Person {
  10. constructor (readonly name: string) {}
  11. }

静态属性 和 静态方法

在 ES6 中只有静态方法,没有静态属性。不过在 typescript 中支持静态属性。
使用 static 修饰符修饰的属性和方法分别称为静态属性和静态方法。它们不需要实例化,可以直接通过类来调用。静态方法里面没法直接调用类里面的属性。

抽象类

在 typescript 中,用 abstract 定义抽象类和其中的抽象方法。抽象类作为其他派生类的基类使用。

  • 抽象类不允许被实例化。通常需要我们创建子类继承基类,然后可以实例化子类。
  • 抽象类中的抽象方法必须被子类实现。
  • 即使是抽象方法, typescript 的编译结果中,仍然会存在这个类。
  • 不同于接口,抽象类可以包含成员的实现细节。
  • 抽象方法只能放在抽象类里面。
  • 抽象类和抽象方法用来定义标准。
  • 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。抽象方法的语法与接口方法相似。两者都是定义方法签名但不包含方法体。然而,抽象方法必须包含 abstract 关键字并且可以包含访问修饰符。

例子:

  1. abstract class Animal {
  2. abstract makeSound(): void;
  3. move(): void {
  4. console.log("roaming the earch...");
  5. }
  6. }
  7. class Cat extends Animal {
  8. makeSound() {
  9. console.log("miao miao");
  10. }
  11. }
  12. let cat: Animal;
  13. cat = new Animal(); // Error
  14. cat = new Cat();
  15. cat.makeSound();
  16. cat.move();

扩展

类实际上是有两个类型的:

  • 静态部分类型
  • 实例部分类型

拿官方的代码举例:

  1. interface ClockInterface {
  2. currentTime: Date;
  3. setTime(d: Date);
  4. }
  5. class Clock implements ClockInterface {
  6. currentTime: Date;
  7. setTime(d: Date) {
  8. this.currentTime = d;
  9. }
  10. constructor(h: number, m: number) { }
  11. }

在这个例子中,currentTimesetTime 就是实例的类型。
class 里的构造器是静态类型。

这篇文章讲得挺清楚的:typescript类类型-实例部分与静态部分详解