Inheritance

extendssuper

class Animal {
  name: string
  constructor(name: string) {
      this.name = name
  }
  move(distanceInMeters: number = 0) {
    console.log(`Animal moved ${distanceInMeters}m.`);
  }
}

class Dog extends Animal {
 constructor(name: string) {
     super(name)
 }
  bark() {
    super.move()
    console.log("Woof! Woof!");
  }
}

const dog = new Dog('fjdsl')
dog.bark()

Public, private, and protected modifiers

Public by default

默认情况下,所有属性为 public

class Animal {
  public name: string;

  public constructor(theName: string) {
    this.name = theName;
  }

  public move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

TypeScript’s private

只能在类的内部访问,子类不能访问

class Animal {
  private name: string;

  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal("Cat").name; // 外部访问不了,会报错

TypeScript是一种结构类型系统。当我们比较两种不同类型时,无论它们来自何处,如果所有成员的类型都是兼容的,那么我们就说这些类型本身是兼容的。
但是对于privateprotected 的比较会有所不同。他们的必须来源于同一个地方。

class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

class Rhino extends Animal {
  constructor() {
    super("Rhino");
  }
}

class Employee {
  private  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
animal = employee;

Rhino 的 私有成员 name 跟 Animal 来源是一样的,都是 Animal 内部声明的,所以 Rhino 的实例可以赋值给 Animal 的实例。但是对于 Employee 它的私有成员是自己另外声明的,来源不同,所以 Employee 的类型跟 Animal 不一样,所以后者的实例不能赋值给前者。

Understanding protected

private 差不多,但是子类能够访问该属性

class Person {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // Error Property 'name' is protected and only accessible within class 'Person' and its subclasses

可以给 constructor 声明 privateprotected

  • private 表示该类不能被在外部被实例化,即不能被extend,只能在内容通过静态成员的方式进行实例化
  • protected 表示不能再外部实例化,可以在子类中被实例化
class Person {
  protected name: string;
  protected constructor(theName: string) {
    this.name = theName;
  }
}

// Employee can extend Person
class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John");

Readonly modifier

您可以使用readonly关键字将属性设置为只读。只读属性必须在其声明或构造函数中进行初始化。

class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;

  constructor(theName: string) {
    this.name = theName;
  }
}

Parameter properties

帮我们简化声明了属性之后,又在构造函数中对其进行复制的过程。支持的修饰符有publicprivateprotectedreadonly

class Person {
    constructor(
        public readonly name: string,
        private age: number,
        protected height: number
    ) {

    }
}
const person = new Person('xxx', 11, 21)

Accessors

给某个成员设置 getters/setters。如果只设置 getter 相当于 readonly

class Employee {
  private _fullName: string = "";

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (newName && newName.length > 10) {
      throw new Error("fullName has a max length of " + fullNameMaxLength);
    }

    this._fullName = newName;
  }
}

Static Properties

  • 所有该类的实例都可以通过 类.静态属性 来访问静态属性,且可以修改。
  • 在类的实例上不会包含静态属性。由于 static 属性是挂载在原型上的,所以访问时通过 this.xxx 也是可以访问的。
class Grid {
  static origin = { x: 0, y: 0 };

  calculateDistanceFromOrigin(point: { x: number; y: number }) {
    let xDist = point.x - Grid.origin.x;
    let yDist = point.y - Grid.origin.y;
    return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }

  constructor(public scale: number) {}
}

let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale

Grid.origin = { x: 1, y: 2 }
console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));

Abstract Classes

  • abstract 类是派生类的基类
  • 不能直接被实例化
  • 跟 interface 不同的是, abstract 类可能会包含成员的具体实现
  • abstract 关键词不仅可以用来声明抽象 class,还可以在 class 内部使用它声明抽象方法
  • 抽象类中的抽象方法在子类中必须实现
abstract class Department {
  constructor(public name: string) {}

  printName(): void {
    console.log("Department name: " + this.name);
  }

  abstract printMeeting(): void; // must be implemented in derived classes
}

class AccountingDepartment extends Department {
  constructor() {
    super("Accounting and Auditing"); // constructors in derived classes must call super()
  }

  printMeeting(): void {
    console.log("The Accounting Department meets each Monday at 10am.");
  }

  generateReports(): void {
    console.log("Generating accounting reports...");
  }
}

let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: Property 'generateReports' does not exist on type 'Department'

Advanced Techniques

Constructor functions

class Greeter {
  static x: number = 11
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter: Greeter; // Greeter 当做类型使用
greeter = new Greeter("world"); // 类型匹配

let gree: typeof Greeter  = new Greeter("word") // error:前者包括了静态类型,后者为实例类型不包括静态类型
let newGree: typeof Greeter  = Greeter // newGree 跟 Greeter 一模一样
  • 声明的 Greeter 也是一个类型。可以当做一个类型来使用。该类型是该 Greeter 的实例类型。
  • 因为是实例,所以不会包含静态成员的类型。只能通过 typeof Greeter 来获取整个类的类型(静态+实例
  • 声明的 Greeter 也是一个构造函数,因为被转换为 ES5 后其实就是 function Greeter{}

Using a class as an interface

因为前面说过,创建 class 时会创建一个代表该 class 实例的类型,所以 class 可以当做类型,也可以被 interface 继承

class Point {
  x: number;
  y: number;
}

interface Point3d extends Point {
  z: number;
}

let point3d: Point3d = { x: 1, y: 2, z: 3 };