类
TypeScript的类与CMAScript 2015的类相似
类
class Greeter {greeting: string;constructor(message: string) {this.greeting = message;}greet() {return "Hello, " + this.greeting;}}let greeter = new Greeter("world");
继承
与ECMAScript2015相似
class Animal {name: string;constructor(theName: string) { this.name = theName; }move(distanceInMeters: number = 0) {console.log(`${this.name} moved ${distanceInMeters}m.`);}}class Snake extends Animal {constructor(name: string) { super(name); }move(distanceInMeters = 5) {console.log("Slithering...");super.move(distanceInMeters);}}class Horse extends Animal {constructor(name: string) { super(name); }move(distanceInMeters = 45) {console.log("Galloping...");super.move(distanceInMeters);}}let sam = new Snake("Sammy the Python");let tom: Animal = new Horse("Tommy the Palomino");sam.move();tom.move(34);
公共,私有与受保护的修饰符
- 默认的public修饰符
class Animal {public name: string;public constructor(theName: string) { this.name = theName; }public move(distanceInMeters: number) {console.log(`${this.name} moved ${distanceInMeters}m.`);}}
- private修饰符:当类的成员被标记为private时,它就不能在声明它的类的外部访问。
class Animal {private name: string;constructor(theName: string) { this.name = theName; }}new Animal("Cat").name; // 错误: 'name' 是私有的.
TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。
然而,当我们比较带有 private或 protected成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected成员也使用这个规则
class Animal {private name: string;constructor(theName: string) { this.name = theName; }}class Rhino extends Animal {constructor() { super("Rhino"); }}class Employee {private name: string;constructor(theName: string) { this.name = theName; }}let animal = new Animal("Goat");let rhino = new Rhino();let employee = new Employee("Bob");animal = rhino;animal = employee; // 错误: Animal 与 Employee 不兼容.
这个例子中有 Animal和 Rhino两个类, Rhino是 Animal类的子类。 还有一个 Employee类,其类型看上去与 Animal是相同的。 我们创建了几个这些类的实例,并相互赋值来看看会发生什么。 因为 Animal和 Rhino共享了来自 Animal里的私有成员定义 private name: string,因此它们是兼容的。 然而 Employee却不是这样。当把 Employee赋值给 Animal的时候,得到一个错误,说它们的类型不兼容。 尽管 Employee里也有一个私有成员 name,但它明显不是 Animal里面定义的那个。
- protected修饰符
protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。
class Person {protected name: string;constructor(name: string) { this.name = name; }}class Employee extends Person {private department: string;constructor(name: string, department: string) {super(name)this.department = department;}public getElevatorPitch() {return `Hello, my name is ${this.name} and I work in ${this.department}.`;}}let howard = new Employee("Howard", "Sales");console.log(howard.getElevatorPitch());console.log(howard.name); // 错误
protected用于构造函数,此时这个类不能在包含它的类外被实例化,但是能被继承
class Person {protected name: string;protected constructor(theName: string) { this.name = theName; }}// Employee 能够继承 Personclass Employee extends Person {private department: string;constructor(name: string, department: string) {super(name);this.department = department;}public getElevatorPitch() {return `Hello, my name is ${this.name} and I work in ${this.department}.`;}}let howard = new Employee("Howard", "Sales");let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.
readonly修饰符:可以使用readonly关键字将属性设置为只读的,只读属性必须在声明时或构造函数里被初始化。
class Octopus {readonly name: string;readonly numberOfLegs: number = 8;constructor (theName: string) {this.name = theName;}}let dad = new Octopus("Man with the 8 strong legs");dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.
参数属性
在上面的例子中,我们必须在Octopus类里定义一个只读成员 name和一个参数为 theName的构造函数,并且立刻将 theName的值赋给 name,这种情况经常会遇到。 参数属性可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前 Octopus类的修改版,使用了参数属性:
class Octopus {readonly numberOfLegs: number = 8;constructor(readonly name: string) {}}
注意看我们是如何舍弃了 theName,仅在构造函数里使用 readonly name: string参数来创建和初始化 name成员。 我们把声明和赋值合并至一处。
参数属性通过给构造函数参数前面添加一个访问限定符来声明。 使用 private限定一个参数属性会声明并初始化一个私有成员;对于 public和 protected来说也是一样。
存取器
let passcode = "secret passcode";class Employee {private _fullName: string;get fullName(): string {return this._fullName;}set fullName(newName: string) {if (passcode && passcode == "secret passcode") {this._fullName = newName;}else {console.log("Error: Unauthorized update of employee!");}}}let employee = new Employee();employee.fullName = "Bob Smith";if (employee.fullName) {alert(employee.fullName);}
存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
静态属性
我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。
class Grid {static origin = {x: 0, y: 0};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;}constructor (public scale: number) { }}let grid1 = new Grid(1.0); // 1x scalelet grid2 = new Grid(5.0); // 5x scaleconsole.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
