TypeScript 文档学习

TypeScript 04 - 类

2019-11-28  本文已影响0人  晓风残月1994
  1. 基本示例
  2. 继承
  3. 公共、私有与受保护的修饰符
  4. readonly 修饰符
  5. 存取器
  6. 静态属性
  7. 抽象类
  8. 高级技巧

1. 基本示例

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

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

let greeter = new Greeter('world');
console.log(greeter.greet());

2. 继承

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

class Animal {
  name: string;

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

  move(distance: number = 0) {
    console.log(`${this.name} moved ${distance}`);
  }
}


class Snake extends Animal {
  constructor(name: string) {
    super(name);
  }

  move(distance: number = 5) {
    console.log('蛇皮走位ing...');
    super.move(distance);
  }
}


class Horse extends Animal {
  constructor(name: string) {
    super(name);
  }

  move(distance: number = 45) {
    console.log('奔腾ing...')
    super.move(distance);
  }
}


let tony = new Snake('Tony');
let tom: Animal = new Horse('Tom');

tony.move();
tom.move(75);
// 蛇皮走位ing...
// Tony moved 5
// 奔腾ing...
// Tom moved 75

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

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

3.1 private 私有修饰符

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

class Animal {
  protected name: string;

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

new Animal('Cat').name; // 报错


class Pig extends Animal {
  constructor() {
    super('Peng');
  }
}


class Employee {
  protected name: string;

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

// 私有成员来源不一样,不能互相兼容,除非成员完全一样

let animal = new Animal('name');
let pig = new Pig();
let employee = new Employee('Bob');

animal = pig; // 子类可以赋值给父类
animal = employee;
// 报错,就算 Employee 看起来和 Animal 定义的一样,但是因为存在用 private 修饰的各自私有属性,所以两个类不能等价
// 除非 name 都是 public,不再私有了,才能认为所指的是同一个属性 

3.2 protected 受保护的修饰符

class Person {
  protected name: string;

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


class Employee extends Person {
  private department: string;

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

  getEmployeeInfo() {
    new Person('wangpeng')
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}


let tony = new Employee('name', 'Sales');
console.log(tony.getEmployeeInfo());
// Hello, my name is name and I work in Sales


console.log(tony.name);
// 报错,受保护的(protected)属性 name 来自于超类 Person,只能在 Person 类中和其子类中访问
let bob = new Person('peng');
// 报错,同理,因为 Person 类的构造器也是 protected

4. readonly 只读修饰符

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


class Person {
  readonly name: string;

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


// Person 类的构造器中还可以使用参数属性,把声明和赋值合并为一个操作,但是不推荐使用
class Person {
  constructor( readonly name: string) {
    this.name = name;  
  }
}


let tom = new Person('Tom');
console.log(tom.name);

tom.name = 'Sam'; // 报错,只读属性不能修改

5. 存取器

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

编译时指定 ES5 版本:

tsc index.ts --target es5

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

var password = 'daohaotiandaleipi';
var Employee = /** @class */ (function () {
    function Employee() {
    }
    Object.defineProperty(Employee.prototype, "fullName", {
        get: function () {
            return this._fullName;
        },
        set: function (newName) {
            if (password && password === 'daohaotiandaleipi') {
                this._fullName = newName;
            }
            else {
                console.log('Error: Unauthorized update of employee!');
            }
        },
        enumerable: true,
        configurable: true
    });
    return Employee;
}());
var employee = new Employee();
employee.fullName = 'Tony'; // 设置前时候会校验密码
if (employee.fullName) {
    console.log(employee.fullName);
}

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

let password = 'daohaotiandaleipi';
class Employee {
    get fullName() {
        return this._fullName;
    }
    set fullName(newName) {
        if (password && password === 'daohaotiandaleipi') {
            this._fullName = newName;
        }
        else {
            console.log('Error: Unauthorized update of employee!');
        }
    }
}
let employee = new Employee();
employee.fullName = 'Tony'; // 设置前时候会校验密码
if (employee.fullName) {
    console.log(employee.fullName);
}

6. 静态属性

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

class Grid {
  static origin = { x: 0, y: 0 }; // 静态属性

  scale: number; // 缩放比例

  constructor(scale: number) {
    this.scale = scale;
  }

  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;
  }
}

let grid1 = new Grid(1.0);
console.log(grid1.calculateDistanceFromOrigin({ x: 3, y: 4 }));

let grid2 = new Grid(5.0);
console.log(grid2.calculateDistanceFromOrigin({ x: 3, y: 4 }));

7. 抽象类

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

abstract class Department {
  name: string;

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

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

  abstract printMeeting(): void;
}


class AccountingDepartment extends Department {
  constructor() {
    super('会计事务所');
  }

  printMeeting(): void {
    console.log('每天十点开会');
  }

  generateReports(): void {
    console.log('生成报告中...');
  }

  // printName(): void {
  //   console.log('Department name: ' + '我瞎编的部门');
  // }
}

let department: Department;

department = new Department(); // 报错,不能直接实例化抽象类

department = new AccountingDepartment();
department.printName();
department.printMeeting();

// 报错,generateReports 并不存在于 Department 抽象类中,而是存在于 AccountingDepartment
// 除非把 department 类型声明为 AccountingDepartment
department.generateReports();

8. 高级技巧

8.1 构造函数

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

class Greeter {
  static standardGreeting = 'Hello, there';
  greeting: string;
  greet() {
    if (this.greeting) {
      return 'Hello, ' + this.greeting;
    } else {
      return Greeter.standardGreeting;
    }
  }
}

let greeter1: Greeter; // 声明 greeter1 是 Greeter 类的实例类型,也叫 Greeter
greeter1 = new Greeter();
console.log(greeter1.greet());

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

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

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

接着就可以 new greeterMaker()

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

let greeterMaker: typeof Greeter;
greeterMaker = Greeter;
greeterMaker.standardGreeting = 'Hey there';

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

console.log(greeter1.greet());

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

Hello, there
Hey hahah
Hey hahah

8.2 把类当做接口使用

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

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

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};
上一篇下一篇

猜你喜欢

热点阅读