TypeScript中的类

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-09-24

类的概念

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

ES6中类的用法

继承

class Cat extends Animal {
  constructor(name) {
    super(name); // 调用父类的 constructor(name)
    console.log(this.name);
  }

  sayHi() {
    return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
  }
}

存储器

使用 gettersetter 改变属性的赋值和读取行为。

class Animal {
  constructor(name) {
    this.name = name;
  }

  get name() {
    return 'Jack';
  }

  set name(value) {
    console.log('setter: ' + value);
  }
}

静态方法

class Animal {
  static isAnimal(a) {
    return a instanceof Animal;
  }
}

let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function”

ES7的一些用法

实例属性

可以直接在类中定义实例属性并给初始值,如果没有给初始值,只是声明了name: string, 那么一定要在构造函数中给初始值。

class Animal {
  // ES6中的实例属性只能在构造函数中使用 `this.xxx` 来定义,ES7中可以直接在类中定义并给初始值
  name = 'Jack';

  constructor() {
    // ...
  }
}

let a = new Animal();
console.log(a.name); // Jack

静态属性

class Animal {
  static num = 42;

  constructor() {
    // ...
  }
}

console.log(Animal.num); // 42

TypeScript中类的用法

访问修饰符

TypeScript中修饰符也分为三种:

  • public:修饰的属性和方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是公有的。
  • private:修饰的属性和方法是私有的,不能在声明它的类的外部访问。
  • protected:修饰的属性和方法是受保护的,它和 private类似,区别是它在子类中也是允许被访问的。

需要注意的是,实际上,修饰符修饰的属性都是实例属性,即一切定义在类中的属性,除了 static 修饰的都是实例属性。

相关示例:

1、编译时:不能在实例上访问私有属性

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

let a = new Animal('Jack');
console.log(a.name); 
// 编译报错: 属性“name”为私有属性,只能在类“Animal”中访问。

需要注意的是,TypeScript编译之后的代码中,并没有限制 private 属性在外部的可访问性,上面的代码依然能正常打印出 'Jack',编译后如下:

"use strict";
class Animal {
  constructor(name) {
    this.name = name;
  }
}
let a = new Animal('Jack');
// console.log(a.name); 

2、编译时:不能在子类中访问父类私有属性

class Cat extends Animal {
  constructor(name: string) {
    super(name)
    console.log(this.name);
    // 子类自己没有name属性,则访问父类的
    // 编译报错: 属性“name”为私有属性,只能在类“Animal”中访问。
  }
}

3、编译时:可以在子类中访问 protected 修饰的属性,但是不能在实例上访问

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

let a = new Animal('Jack');
console.log(a.name); // 编译报错,属性“name”受保护,只能在类“Animal”及其子类中访问。

class Cat extends Animal {
  constructor(name: string) {
    super(name)
    console.log(this.name); // 通过编译
  }
}

4、当构造函数修饰符为 private 时,该类不允许被继承或者实例化:

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

let a = new Animal('Jack'); // Error 类“Animal”的构造函数是私有的,仅可在类声明中访问。

class Cat extends Animal { // Error 无法扩展类“Animal”。类构造函数标记为私有。
  constructor(name: string) {
    super(name);
  }
}

5、当构造函数修饰符为 protected 时,该类可以被继承,但是不能实例化

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

let a = new Animal('Jack'); // Error 类“Animal”的构造函数是受保护的,仅可在类声明中访问。
 
class Cat extends Animal { // 通过编译
  constructor(name: string) {
    super(name);
  }
}

参数属性

修饰符和readonly 可以使用在构造函数参数中,等同于在类中定义该属性同时给该属性赋值

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

// 等同于, 使代码更简洁
class Animal {
  constructor(public name: string) {}
}

readonly

只读属性关键字,只允许出现在属性声明或索引签名或构造函数参数中。

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

let a = new Animal('jack')
console.log(a.name); // jack
a.name = 'tom'; // error 无法分配到 "name" ,因为它是只读属性。

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

class Animal {
  public readonly name: string
  public constructor(name: string) {
    this.name = name;
  }
}

抽象类

abstract 用于定义抽象类和其中的抽象方法(抽象方法只能出现在抽象类中),什么是抽象类?首先,抽象类是不允许被实例化;其次,抽象类的方法必须由子类实现。

abstract class Animal {
  public name;
  public constructor(name: string) {
    this.name = name;
  }
  public abstract sayHi(): void;
}

let a = new Animal('jack') // error 无法创建抽象类的实例。

class Cat extends Animal { // error 非抽象类“Cat”不会实现继承自“Animal”类的抽象成员“sayHi”。
  public eat() {
    console.log(`${this.name} is eating.`);
  }
}

let cat = new Cat('Tom');

需要注意的是,抽象类中的抽象方法是不会编译的,而只定义eat方法,编译报错,最终编译结果还是会包含 eat 方法。