TypeScript 初识 - 类
一个普通的类
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return 'Hello, ' + this.greeting; } } let greeter = new Greeter('world');
继承
super
作为函数调用时,代表父类的构造函数。子类的构造函数必须执行一次 super
函数,并且在构造函数里访问 this
的属性之前一定要调用 super()
:
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); } }
super
虽然代表了父类 Animal
的构造函数,但是返回的是子类 Snake
的实例,即 super
内部的 this
指的是 Snake
的实例,因此 super()
在这里相当于 Animal.prototype.constructor.call(this)
。
访问修饰符
public
在 TypeScript 中,所有访问修饰符默认为 public
,这个和 JavaScript 是一致的:
// 这和上面例子是一致的 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
意为私有,使用 private
修饰的变量,不允许在类的外面使用,子类中也不允许访问:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal('Cat').name; // Error
在 TypeScript 中,如果两个类的所有成员的类型都是兼容的,这就表示两个类是兼容的。
但是,一个类里有 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; // 不能将类型“Employee”分配给类型“Animal”。类型具有私有属性“name”的单独声明。
protected
protected
唯一比 private
修饰的成员多出的权限在于,protected
修饰的成员可以在子类中使用,但仍不允许在类的外面使用。
构造函数可以被 protected
修饰,修饰后的类无法被实例化,只能被继承。
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 是只读的
参数属性
参数属性可以方便地在一个类里定义并初始化一个成员:
// 这里和上面定义的类是一样的 class Octopus { readonly numberOfLegs: number = 8; // 把声明和赋值合并到一起 constructor(readonly name: string) {} }
参数属性通过给构造函数参数前面添加一个访问限定符来声明。使用 private
限定一个参数属性会声明并初始化一个私有成员;public
和 protected
也是一样。
存取器
TypeScript 中的类支持取值函数(getter)、存值函数(setter):
let passCode = 'secret passCode'; class Employee { // 私有属性 private _fullName: string; // getter get fullName(): string { return this._fullName; } // setter 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); }
存取器必须要编译器设置(compilerOptions.target)输出为 ES6 或更高。
只带有 get
不带有 set
的存取器会自动被推断为 readonly
。
静态属性
TypeScript 中的类支持静态成员,可以在没有实例化的情况下进行访问,使用 static
进行修饰:
class Grid { static origin = { x: 0, y: 0 }; calculateDistanceFromOrigin(point: { x: number; y: number }) { // 类里使用静态成员需要加上类名,使用访问和 this 相同 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 grid = new Grid(1.0); console.log(grid.calculateDistanceFromOrigin({ x: 10, y: 10 })); // 14.142135623730951 console.log(Grid.origin); // Object {x: 0, y: 0}
静态方法调用不了实例化方法和实例化属性,因为静态域加载是在解析阶段,而实例化是在初始化阶段,所以静态方法里面不能调用本类的方法和属性,可以调用静态属性和静态方法。
抽象类
抽象类做为其它字类的基类使用,一般不会直接被实例化。不同于接口,抽象类可以包含成员的实现细节。abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法:
abstract class Animal { abstract makeSound(): void; move(): void { console.log('moving...'); } }
抽象类中的抽象方法不包含具体实现并且必须在字类中实现。抽象方法必须包含 abstract
关键字并且可以包含访问修饰符。
类和接口
类定义会创建两个东西:类的实例类型和一个构造函数。因为类可以创建出类型,所以可以在使用接口的地方使用类:
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } let point3d: Point3d = { x: 1, y: 2, z: 3 };
类可以实现(implement
)接口。通过接口可以强制指明类遵守某个契约。也可以在接口中声明一个方法,然后要求类去具体实现它。
接口不可以被实例化,实现接口必须重写接口中的抽象方法。
类可以实现(implement
)多个接口,但只能扩展(extends
)自一个抽象类。
抽象类中可以包含具体实现,接口不能。
抽象类在运行时是可见的,可以通过 instanceof
判断。接口则只在编译时起作用。
接口只能描述类的公共(public
)部分,不会检查私有成员,而抽象类没有这样的限制。