1. class Dog {
  2. name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. bark() {
  7. console.log("Woof! Woof!");
  8. }
  9. }
  10. const dog = new Dog("Q");
  11. dog.bark(); // => 'Woof! Woof!'

继承

和 js 一致,派生类如果包含一个构造函数,则必须在构造函数中调用 super() 方法。访问派生类的构造函数中的 “this” 前,必须调用 “super”。

  1. class Animal {}
  2. class Dog extends Animal {
  3. name: string;
  4. constructor(name: string) {
  5. // ts(2377) Constructors for derived classes must contain a 'super' call.
  6. this.name = name;
  7. }
  8. bark() {
  9. console.log("Woof! Woof!");
  10. }
  11. }
  12. class Dog1 extends Animal {
  13. name: string;
  14. constructor(name: string) {
  15. super(); // 添加 super 方法
  16. this.name = name;
  17. }
  18. bark() {
  19. console.log("Woof! Woof!");
  20. }
  21. }

公共、私有与受保护的修饰符

在 TypeScript 中就支持 3 种访问修饰符,分别是 public、private、protected。

  • public 修饰的是在任何地方可见、公有的属性或方法;
  • private 修饰的是仅在同一类中可见、私有的属性或方法;
  • protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法。
  1. class Son {
  2. public firstName: string;
  3. private lastName: string = "Stark";
  4. constructor(firstName: string) {
  5. this.firstName = firstName;
  6. this.lastName; // ok
  7. }
  8. }
  9. const son = new Son("Tony");
  10. console.log(son.firstName); // => "Tony"
  11. son.firstName = "Jack";
  12. console.log(son.firstName); // => "Jack"
  13. console.log(son.lastName); // ts(2341) Property 'lastName' is private and only accessible within class 'Son'.

注意:TypeScript 中定义类的私有属性仅仅代表静态类型检测层面的私有。如果我们强制忽略 TypeScript 类型的检查错误,转译且运行 JavaScript 时依旧可以获取到 lastName 属性,这是因为 JavaScript 并不支持真正意义上的私有属性。

  1. class Son {
  2. public firstName: string;
  3. protected lastName: string = "Stark";
  4. constructor(firstName: string) {
  5. this.firstName = firstName;
  6. this.lastName; // ok
  7. }
  8. }
  9. class GrandSon extends Son {
  10. constructor(firstName: string) {
  11. super(firstName);
  12. }
  13. public getMyLastName() {
  14. return this.lastName;
  15. }
  16. }
  17. const grandSon = new GrandSon("Tony");
  18. console.log(grandSon.getMyLastName()); // => "Stark"
  19. grandSon.lastName; // 属性“lastName”受保护,只能在类“Son”及其子类中访问。ts(2445)

只读修饰符

在前面的例子中,Son 类 public 修饰的属性既公开可见,又可以更改值,如果我们不希望类的属性被更改,则可以使用 readonly 只读修饰符声明类的属性,如下代码所示:

  1. class Son {
  2. public readonly firstName: string;
  3. constructor(firstName: string) {
  4. this.firstName = firstName;
  5. }
  6. }
  7. const son = new Son("Tony");
  8. son.firstName = "Jack"; // 无法分配到 "firstName" ,因为它是只读属性。ts(2540)

注意:如果只读修饰符和可见性修饰符同时出现,我们需要将只读修饰符写在可见修饰符后面。

存取器

和 js 一致

  1. class Son {
  2. public firstName: string;
  3. protected lastName: string = 'Stark';
  4. constructor(firstName: string) {
  5. this.firstName = firstName;
  6. }
  7. }
  8. class GrandSon extends Son {
  9. constructor(firstName: string) {
  10. super(firstName);
  11. }
  12. get myLastName() {
  13. return this.lastName;
  14. }
  15. set myLastName(name: string) {
  16. if (this.firstName === 'Tony') {
  17. this.lastName = name;
  18. } else {
  19. console.error('Unable to change myLastName');
  20. }
  21. }
  22. }
  23. const grandSon = new GrandSon('Tony');
  24. console.log(grandSon.myLastName); // => "Stark"
  25. grandSon.myLastName = 'Rogers';
  26. console.log(grandSon.myLastName); // => "Rogers"
  27. const grandSon1 = new GrandSon('Tony1');
  28. grandSon1.myLastName = 'Rogers'; // => "Unable to change myLastName"

静态属性

和 js 一致

  1. class MyArray {
  2. static displayName = 'MyArray';
  3. static isArray(obj: unknown) {
  4. return Object.prototype.toString.call(obj).slice(8, -1) === 'Array';
  5. }
  6. }
  7. console.log(MyArray.displayName); // => "MyArray"
  8. console.log(MyArray.isArray([])); // => true
  9. console.log(MyArray.isArray({})); // => false

基于静态属性的特性,我们往往会把与类相关的常量、不依赖实例 this 上下文的属性和方法定义为静态属性,从而避免数据冗余,进而提升运行性能。

注意:上边我们提到了不依赖实例 this 上下文的方法就可以定义成静态方法,这就意味着需要显式注解 this 类型才可以在静态方法中使用 this;非静态方法则不需要显式注解 this 类型,因为 this 的指向默认是类的实例

抽象类

接下来我们看看关于类的另外一个特性——抽象类,它是一种不能被实例化仅能被子类继承的特殊类。
我们可以使用抽象类定义派生类需要实现的属性和方法,同时也可以定义其他被继承的默认属性和方法,如下代码所示:

  1. abstract class Adder {
  2. abstract x: number;
  3. abstract y: number;
  4. abstract add(): number;
  5. displayName = 'Adder';
  6. addTwice(): number {
  7. return (this.x + this.y) * 2;
  8. }
  9. }
  10. class NumAdder extends Adder {
  11. x: number;
  12. y: number;
  13. constructor(x: number, y: number) {
  14. super();
  15. this.x = x;
  16. this.y = y;
  17. }
  18. add(): number {
  19. return this.x + this.y;
  20. }
  21. }
  22. const numAdder = new NumAdder(1, 2);
  23. console.log(numAdder.displayName); // => "Adder"
  24. console.log(numAdder.add()); // => 3
  25. console.log(numAdder.addTwice()); // => 6

任何继承 Adder 的派生类都需要实现这些 abstract 的抽象属性和方法。

实际上,我们也可以定义一个描述对象结构的接口类型抽象类的结构,并通过 implements 关键字约束类的实现。

使用接口与使用抽象类相比,区别在于接口只能定义类成员的类型,如下代码所示:

接口不能定义其他被继承的默认属性和方法

  1. 1. interface IAdder {
  2. 2. x: number;
  3. 3. y: number;
  4. 4. add: () => number;
  5. 5. }
  6. 6. class NumAdder implements IAdder {
  7. 7. x: number;
  8. 8. y: number;
  9. 9. constructor(x: number, y: number) {
  10. 10. this.x = x;
  11. 11. this.y = y;
  12. 12. }
  13. 13. add() {
  14. 14. return this.x + this.y;
  15. 15. }
  16. 16. addTwice() {
  17. 17. return (this.x + this.y) * 2;
  18. 18. }
  19. 19. }

类的类型

类的最后一个特性——类的类型和函数类似,即在声明类的时候,其实也同时声明了一个特殊的类型(确切地讲是一个接口类型),这个类型的名字就是类名,表示类实例的类型;在定义类的时候,我们声明的除构造函数外所有属性、方法的类型就是这个特殊类型的成员。如下代码所示:

  1. class A {
  2. name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. }
  7. const a1: A = {}; // ts(2741) Property 'name' is missing in type '{}' but required in type 'A'.
  8. const a2: A = { name: 'a2' }; // ok