一.类成员
TypeScript里的类的定义与ES6 Class规范一致,静态属性,实例属性,访问器等都支持:
class Grid {
static origin = {x: 0, y: 0};
}
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
但需要注意2点:
ES3不支持getter/setter,因此要求编译配置为ES5+
只有getter没有setter的属性会被自动推断为
readonly
(成员修饰符之一)
二.成员修饰符
访问控制修饰符
支持3个访问控制修饰符:
public:类的成员属性/方法默认都是
public
,没有访问限制private:无法在该类声明的外部访问其成员(如无法通过
this.xxx
访问私有成员)protected:与
private
类似,但在派生类中也可以访问受保护成员
例如:
class Animal {
// 私有成员属性
private name: string;
// 受保护成员属性
protected ancestor: string;
// 不写的话,默认public
constructor(theName: string) { this.name = theName; }
// public成员方法
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
注意,这些访问控制都只是编译时的限制,运行时并不做强检查。符合TypeScript的设计原则:
不给编译产物增加运行时开销
另外,类成员可访问性也是类型检查的一部分,private/protected
修饰符会打破鸭子类型,例如:
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
}
class Person {
name: string;
constructor(theName: string) { this.name = theName; }
}
// 正确:鸭子类型(长得像就是)
let person: Animal = new Person('Sam');
class Plant {
// 私有成员name
private name: string;
constructor(theName: string) { this.name = theName; }
}
// 错误:name属性可访问性不匹配(长得像也不行,可访问性不一样)
// Property 'name' is private in type 'Plant' but not in type 'Person'.
let invalidPlant: Plant = new Person('Stone');
P.S.特殊的,protected constructor
表示该类不允许直接实例化,但支持继承
readonly修饰符
可以通过readonly
修饰符声明属性只读,只能在声明时或构造函数里赋值,例如:
class Octopus {
readonly name: string;
public 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"; // error! name is readonly.
P.S.当然,readonly
与访问控制修饰符并不冲突,可以作用于同一个属性
参数属性
对于在构造函数里初始化的属性:
class Octopus {
readonly name: string;
constructor (theName: string) {
this.name = theName;
}
}
有一种简写方式:
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
其中,name
叫参数属性,通过给构造函数的形参名前添上private/protected/public/readonly
修饰符来声明
三.继承
class A extends B {
//...
}
类似于Babel转换(清晰起见,仅摘录关键部分):
function _inherits(subClass, superClass) {
// 继承父类原型属性
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
// 继承父类静态属性
if (superClass)
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
// 子类构造函数中继承父类实例属性
function A() {
// 通过父类构造函数给子类实例this添上父类实例属性
return A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments)
}
TypeScript里的Class继承也会被编译替换成基于原型的继承,如下:
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
// 继承父类静态属性
extendStatics(d, b);
function __() { this.constructor = d; }
// 继承父类原型属性,及实例属性
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
// __extends(A, B);
二者大同小异,从实现上看,TypeScript编译产物更健壮,因为其目标是:
在任何支持 ES3+的宿主环境中运行
P.S.比较有意思的是静态属性的继承,具体见一.如何继承静态属性?
四.抽象类
TypeScript里也有抽象类的概念:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
抽象类里可以有带实现的具体方法(如move
),也可以有只声明不实现的抽象方法(如makeSound
),但要求子类必须实现这些方法:
class Cat extends Animal {
makeSound() {
console.log('meow meow meow');
}
}
另一个相似的概念是接口,二者区别在于接口中只能定义“抽象方法”(没有abstract
关键字,确切地说是方法签名),例如:
interface Animal {
// 对比 abstract makeSound(): void;
makeSound(): void;
}
// 对比 class Cat extends Animal
class Cat implements Animal {
makeSound() {
console.log('meow meow meow');
}
}
五.类与类型
声明一个类的同时,也声明了该类实例的类型,例如:
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter: Greeter;
greeter = new Greeter("world");
其中,实例greeter
是Greetr
类型的,也就是说,Class声明具有类型含义:
该类实例的类型:
Greeter
类自身的类型:
typeof Greeter
实际上,类自身的类型约束了静态属性、实例属性、构造函数、原型方法等特征,例如:
class GreeterDuck {
// 类自身的类型约束
static standardGreeting = 'Hi';
greeting: string;
constructor(message: string) { }
greet() { return 'there'; }
// 允许多不允许少(鸭子类型)
sayHello() { /*...*/ }
}
let greeterType: typeof Greeter = GreeterDuck;
更进一步的:
// 从Class类型扩展出Interface
interface HelloGreeter extends Greeter {
sayHello(): void;
}
let hGreeter: HelloGreeter = new GreeterDuck('world');
没错,因为类具有类型含义,所以接口能够继承自类