- 基本示例
- 继承
- 公共、私有与受保护的修饰符
- readonly 修饰符
- 存取器
- 静态属性
- 抽象类
- 高级技巧
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 Salesconsole.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 类型声明为 AccountingDepartmentdepartment.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 类的实例类型,也叫 Greetergreeter1 = 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());
总的输出结果如下,说明其实 greeterMaker 和 Greeter 引用的都是同一个值
Hello, thereHey hahahHey hahah
8.2 把类当做接口使用
就像上一章提到的接口继承类,虽然类能当做接口使用,并且能被接口继承,但通常不建议这样做。
class Point {x: number;y: number;}interface Point3d extends Point {z: number;}let point3d: Point3d = {x: 1, y: 2, z: 3};
