类本身就是一种类型,类的名字可以直接作为类型名:
// 定义类
class TypeA {
// ...
}
// 声明TypeA类型
let a: TypeA;
// 赋值TypeA类型
a = new TypeA();
在ES6中,实例属性和静态属性不能直接定义在类内部(有新提案通过了可以在内部直接声明实例属性和静态属性的新写法):
// 下面是ES6代码
// 不合法的定义
class Greeter {
// 错误,实例属性不能定义在类里
greeting = 'world';
// 错误,静态属性不能定义在类里
static greeting = 'world';
}
// 合法的定义
class Greeter {
constructor(){
// 正确,ES6中实例属性只能定义在构造器内部
this.greeting = 'world';
}
}
// 正确,ES6中静态属性只能定义在类外部
Greeter.greeting = 'world';
而在TypeScript中,下面的定义是合法的:
// 下面是TypeScript代码
class Greeter {
// 定义实例属性并初始化
greeting: string = 'world';
// 定义静态属性并初始化
static greeting: string = 'world';
}
在TypeScript中,修饰符不是必须的,他们主要用来控制类成员的可访问性,类成员包括:
- 实例属性
- 静态属性
- 实例方法
- 静态方法
- 构造函数
- getter/setter
如果你没有显式为成员添加访问控制修饰符,那么将默认为 public
class Animal {
// name属性未显式添加修饰符,默认为public
age: number;
// 显式添加 private
private name: string;
// show方法未显式添加修饰符,默认为public
show(){
// ...
}
}
// 与上述定义等价
class Animal {
public age: number;
private name: string;
public show() {
// ...
}
}
当成员被标记成 private
时,它不能在声明它的类外部访问。比如:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
// 错误: 不能在类外访问私有成员
// error TS2341: Property 'name' is private and only accessible within class 'Animal'
new Animal("Cat").name;
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() {
// 正确,来自父类的name成员在派生类里可以访问。虽然它位于Persion类的外部
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
// 错误,受保护的成员 name 不能在外部访问
// error TS2445: Property 'name' is protected and only accessible within class 'Person' and its subclasses
console.log(howard.name);
访问控制修饰符可直接用于构造函数的参数声明,这是一个语法糖,可以非常方便的实现属性的定义并初始化:
class Animal {
// private修饰符用于构造器的name参数前
constructor(private name: string) {}
}
// 以上定义相当于
class Animal {
private name: string;
constructor(name: string) {
this.name = name;
}
}
你可以使用 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");
// 错误! name 是只读的
// error TS2540: Cannot assign to 'name' because it is a constant or a read-only property
dad.name = "Man with the 3-piece suit";
只读属性和常量类似,一旦初始化,之后就再也不允许被赋值。
用于描述抽象类和抽象方法的关键字都是 abstract
,抽象方法没有方法体(也就是不包含实现):
// Animal是抽象类
abstract class Animal {
// makeSound是抽象方法,没有方法体
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
一般情况下,抽象类和抽象方法是同时出现的,但也有例外:
- 一个类包含抽象方法,那么这个类必须是抽象类
- 抽象类可以没有抽象方法
class Animal {
// 错误,有抽象方法,但是类不是抽象类,不符合[1]
// error TS1244: Abstract methods can only appear within an abstract class
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
// 正确,抽象类可以没有抽象方法,符合[2]
// 没有抽象方法的抽象类和一般类没区别
abstract class Animal {
move(): void {
console.log('roaming the earch...');
}
}
抽象类主要是用来被继承使用,抽象方法必须在派生类中必须被实现:
// Animal是抽象类
abstract class Animal {
// makeSound是抽象方法,没有方法体
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
// 错误,抽象方法makeSound没有实现
// error TS2515: Non-abstract class 'Dog' does not implement inherited abstract member 'makeSound' from class 'Animal'
class Dog extends Animal {
// 空类
}
// 正确,抽象方法被实现
class Dog extends Animal {
makeSound(): void{
// ...
}
}
官方文档把构造器类型称为类的静态类型,字面意思比较难理解
构造器类型用来表示具有相同构造器的类,主要是用来描述类的构造者。可以简单的理解为构造器类型的值就是一个类,而本章开篇所讲的类类型的值是一个对象
new (p1: T1, p2: T2, ...) => T
可以看到,构造器类型的语法和函数类型极为相似,区别是在最前面多了一个 new
关键字:
class TypeA {
constructor(name: string) {
// ...
}
}
// 变量b为构造器类型,和类TypeA的构造器兼容
let b: new (name: string) => TypeA;
b = TypeA;
// b现在是一个类
new b('type');
上面的例子实际上相当于为类 TypeA
定义了一个别名 b
。下面再看一个例子:
class TypeA {
constructor(name: string) {
// ...
}
}
// TypeB 类多一个方法 show
class TypeB {
constructor(name: string) {
// ...
}
show(){
// ...
}
}
let c: new (name: string) => TypeA;
// 正确,TypeB除了构造器和TypeA兼容,也兼容TypeA的所有成员
c = TypeB;
let d: new (name: string) => TypeB;
// 错误,TypeA缺少方法show,不能直接赋值给d
// error TS2322: Type 'typeof TypeA' is not assignable to type 'new (name: string) => TypeB'
d = TypeA;
构造器类型在日常开发中使用较少,由于概念比较晦涩,建议只要能看懂语法就行,无须深入