1. 基本示例
  2. 继承
  3. 公共、私有与受保护的修饰符
  4. readonly 修饰符
  5. 存取器
  6. 静态属性
  7. 抽象类
  8. 高级技巧

1. 基本示例

  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');
  11. console.log(greeter.greet());

2. 继承

继承时子类要拥有匹配的构造器,super 关键字直接调用时等价于调用父类构造函数,而 super.props 可以访问父对象上的方法成员。在创建子类实例时,要先调用 super 来生成匹配的父类,并且必须在使用 this 关键字之前使用。 然后才进一步给当前子类自己加戏。

  1. class Animal {
  2. name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. move(distance: number = 0) {
  7. console.log(`${this.name} moved ${distance}`);
  8. }
  9. }
  10. class Snake extends Animal {
  11. constructor(name: string) {
  12. super(name);
  13. }
  14. move(distance: number = 5) {
  15. console.log('蛇皮走位ing...');
  16. super.move(distance);
  17. }
  18. }
  19. class Horse extends Animal {
  20. constructor(name: string) {
  21. super(name);
  22. }
  23. move(distance: number = 45) {
  24. console.log('奔腾ing...')
  25. super.move(distance);
  26. }
  27. }
  28. let tony = new Snake('Tony');
  29. let tom: Animal = new Horse('Tom');
  30. tony.move();
  31. tom.move(75);
  32. // 蛇皮走位ing...
  33. // Tony moved 5
  34. // 奔腾ing...
  35. // Tom moved 75

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

成员默认都是 public 公共的,可以在外部任意访问。

3.1 private 私有修饰符

private 修饰的成员只能在类内部访问,private 修饰的构造器也只有在内部才能使用 new 操作符来实例化:

  1. class Animal {
  2. protected name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. }
  7. new Animal('Cat').name; // 报错
  8. class Pig extends Animal {
  9. constructor() {
  10. super('Peng');
  11. }
  12. }
  13. class Employee {
  14. protected name: string;
  15. constructor(name: string) {
  16. this.name = name;
  17. }
  18. }
  19. // 私有成员来源不一样,不能互相兼容,除非成员完全一样
  20. let animal = new Animal('name');
  21. let pig = new Pig();
  22. let employee = new Employee('Bob');
  23. animal = pig; // 子类可以赋值给父类
  24. animal = employee;
  25. // 报错,就算 Employee 看起来和 Animal 定义的一样,但是因为存在用 private 修饰的各自私有属性,所以两个类不能等价
  26. // 除非 name 都是 public,不再私有了,才能认为所指的是同一个属性

3.2 protected 受保护的修饰符

  1. class Person {
  2. protected name: string;
  3. protected constructor(name: string) {
  4. this.name = name;
  5. }
  6. }
  7. class Employee extends Person {
  8. private department: string;
  9. constructor(name: string, department: string) {
  10. super(name);
  11. this.department = department;
  12. }
  13. getEmployeeInfo() {
  14. new Person('wangpeng')
  15. return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  16. }
  17. }
  18. let tony = new Employee('name', 'Sales');
  19. console.log(tony.getEmployeeInfo());
  20. // Hello, my name is name and I work in Sales
  21. console.log(tony.name);
  22. // 报错,受保护的(protected)属性 name 来自于超类 Person,只能在 Person 类中和其子类中访问
  23. let bob = new Person('peng');
  24. // 报错,同理,因为 Person 类的构造器也是 protected

4. readonly 只读修饰符

readonly 类似于是 Java 中的 final 关键字,用来修饰只能被赋值一次、之后只读的值。

  1. class Person {
  2. readonly name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. }
  7. // Person 类的构造器中还可以使用参数属性,把声明和赋值合并为一个操作,但是不推荐使用
  8. class Person {
  9. constructor( readonly name: string) {
  10. this.name = name;
  11. }
  12. }
  13. let tom = new Person('Tom');
  14. console.log(tom.name);
  15. tom.name = 'Sam'; // 报错,只读属性不能修改

5. 存取器

编译的时候要指定为 ES5 或更高版本,方能支持访问器。

编译时指定 ES5 版本:

  1. tsc index.ts --target es5

编译成 ES5 后,实际上是利用了 Object.defineProperty() 来定义访问器属性描述符,这也叫访问劫持(这些专业术语总是如此专业):

  1. var password = 'daohaotiandaleipi';
  2. var Employee = /** @class */ (function () {
  3. function Employee() {
  4. }
  5. Object.defineProperty(Employee.prototype, "fullName", {
  6. get: function () {
  7. return this._fullName;
  8. },
  9. set: function (newName) {
  10. if (password && password === 'daohaotiandaleipi') {
  11. this._fullName = newName;
  12. }
  13. else {
  14. console.log('Error: Unauthorized update of employee!');
  15. }
  16. },
  17. enumerable: true,
  18. configurable: true
  19. });
  20. return Employee;
  21. }());
  22. var employee = new Employee();
  23. employee.fullName = 'Tony'; // 设置前时候会校验密码
  24. if (employee.fullName) {
  25. console.log(employee.fullName);
  26. }

下面指定编译为 ES6 版本,和 TS 源码区别不大,基本只是把类型系统移除,毕竟 ES6 的 class 本身就支持这样的访问器语法:

  1. let password = 'daohaotiandaleipi';
  2. class Employee {
  3. get fullName() {
  4. return this._fullName;
  5. }
  6. set fullName(newName) {
  7. if (password && password === 'daohaotiandaleipi') {
  8. this._fullName = newName;
  9. }
  10. else {
  11. console.log('Error: Unauthorized update of employee!');
  12. }
  13. }
  14. }
  15. let employee = new Employee();
  16. employee.fullName = 'Tony'; // 设置前时候会校验密码
  17. if (employee.fullName) {
  18. console.log(employee.fullName);
  19. }

6. 静态属性

静态属性存在类本身上,而不是实例上。类其实也是一个对象,使用 SomeClass.someProperty 这种形式来读取静态属性。

  1. class Grid {
  2. static origin = { x: 0, y: 0 }; // 静态属性
  3. scale: number; // 缩放比例
  4. constructor(scale: number) {
  5. this.scale = scale;
  6. }
  7. calculateDistanceFromOrigin(point: { x: number; y: number }) {
  8. let xDist = point.x - Grid.origin.x;
  9. let yDist = point.y - Grid.origin.y;
  10. return Math.sqrt(xDist * xDist + yDist * yDist) * this.scale;
  11. }
  12. }
  13. let grid1 = new Grid(1.0);
  14. console.log(grid1.calculateDistanceFromOrigin({ x: 3, y: 4 }));
  15. let grid2 = new Grid(5.0);
  16. console.log(grid2.calculateDistanceFromOrigin({ x: 3, y: 4 }));

7. 抽象类

抽象类作为其他派生类的基类,不能直接进行实例化,由其派生类来实例化,抽象类可以提供抽象的方法签名,也可以提供一些方法的默认实现

  1. abstract class Department {
  2. name: string;
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. printName(): void {
  7. console.log('Department name: ' + this.name);
  8. }
  9. abstract printMeeting(): void;
  10. }
  11. class AccountingDepartment extends Department {
  12. constructor() {
  13. super('会计事务所');
  14. }
  15. printMeeting(): void {
  16. console.log('每天十点开会');
  17. }
  18. generateReports(): void {
  19. console.log('生成报告中...');
  20. }
  21. // printName(): void {
  22. // console.log('Department name: ' + '我瞎编的部门');
  23. // }
  24. }
  25. let department: Department;
  26. department = new Department(); // 报错,不能直接实例化抽象类
  27. department = new AccountingDepartment();
  28. department.printName();
  29. department.printMeeting();
  30. // 报错,generateReports 并不存在于 Department 抽象类中,而是存在于 AccountingDepartment
  31. // 除非把 department 类型声明为 AccountingDepartment
  32. department.generateReports();

8. 高级技巧

8.1 构造函数

如之前了解的那样,类具有静态部分和实例部分:

  1. class Greeter {
  2. static standardGreeting = 'Hello, there';
  3. greeting: string;
  4. greet() {
  5. if (this.greeting) {
  6. return 'Hello, ' + this.greeting;
  7. } else {
  8. return Greeter.standardGreeting;
  9. }
  10. }
  11. }
  12. let greeter1: Greeter; // 声明 greeter1 是 Greeter 类的实例类型,也叫 Greeter
  13. greeter1 = new Greeter();
  14. console.log(greeter1.greet());

那如果想声明一个变量let greeterMaker,将来会用来存储一个类(在 JS 这门语言中其实就是构造函数),那么 TS 中当给该变量指定类型时, let greeterMaker: Greeter 这样是不行的,这意思曲解成了变量 greeterMaker 的类型是 Greeter 的实例类型。

参见下方代码,此时,可以使用 typeof Greeter 来表明这里我需要的是 Greeter 类本身的类型,或者说是 Greeter 构造函数本身的类型(而不是其实例的类型),这个类型包含了类的所有静态成员和构造函数。

当然如果是声明变量的同时就进行了赋值,那么即使不显式地指出 greeterMaker 的类型,在类似 vscode 这种编辑器中 ,使用鼠标 hover 的时候也会显示 TS 自动推断出的类型: TypeScript 04 - 类 - 图1

接着就可以 new greeterMaker()

把下面代码接着写在上一段代码后面:

  1. let greeterMaker: typeof Greeter;
  2. greeterMaker = Greeter;
  3. greeterMaker.standardGreeting = 'Hey there';
  4. let greeter2: Greeter = new greeterMaker();
  5. console.log(greeter2.greet());
  6. console.log(greeter1.greet());

总的输出结果如下,说明其实 greeterMakerGreeter 引用的都是同一个值

  1. Hello, there
  2. Hey hahah
  3. Hey hahah

8.2 把类当做接口使用

就像上一章提到的接口继承类,虽然类能当做接口使用,并且能被接口继承,但通常不建议这样做。

  1. class Point {
  2. x: number;
  3. y: number;
  4. }
  5. interface Point3d extends Point {
  6. z: number;
  7. }
  8. let point3d: Point3d = {x: 1, y: 2, z: 3};