一个普通的类
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
)部分,不会检查私有成员,而抽象类没有这样的限制。