声明:本学习系列参考了TypeScript3.3英文版官网教程
类(Class)
传统的js使用函数和基于原型的方式来构建可重复使用的组件,es6开始可以使用基于类的继承方式。Typescript允许开发者使用基于类的继承方式,而不必等待新的JavaScript版本。
1、构建第一个类
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
这些语法看起来有点像java或C#,我们使用关键字class
声明了一个新类Greeter
,然后使用new
关键字创建了该类的一个实例。
2、继承
在typescript中使用基于面向对象的模式,一个类可以继承另一个存在的类,从而实现组件的复用。
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
在这个例子中子类Dog
使用extends
关键字继承了父类Animal
的属性和方法,所以子类的就具有了父类的行为,子类的实例就可以使用父类的方法。
让我们开看一个更加复杂一些的例子。
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);
输出:
Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.
在这个例子中我们创建了Animal
的两个子类Snake
,Horse
,并且使用了super
关键字,typescript约定,如果父类有显示声明的构造器,子类在使用this
关键字之前必须先调用super()
来构建父类的实例,如果父类没有显示声明的构造器,则子类默认调用super()
。super.property
也来使用父类的的方法和属性。
这个例子也展示了子类可以使用同名的属性和方法覆盖父类的属性和方法,尽管Horse
的实例被声明为Animal
的类型,但因为它的值是一个Horse
,所以它仍然是调用Horse
的方法。
3、Public,Private和Protected修饰符
3.1、Public是默认的
如果你熟悉其他语言,你可能会发现我们在以上例子中没有使用public
关键字来声明一个成员,这是因为在typescript中每一个成员默认是public
成员的,上面的例子也可以改写成以下形式。
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
3.2、理解Private
当一个成员被声明为一个private
成员的时候,它就不能在它声明的类之外使用,如果一个构造器被声明为一个private
成员,那它就不能在它的类外进行实例化。
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // Error: 'name' is private;
typescript是一个结构形类型系统,当我们比较两种类型的时候,我们会忽略它们的来源,只要它们的成员是兼容的就可以了,然而当比较含有private
和protected
的成员类型时,这种情况就变得不同了。
当我们比较这种类型时,只有当他们的private
成员是来自同一个声明时才被认为是兼容的(同一个类的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; // Error: 'Animal' and 'Employee' are not compatible
3.3、理解Protected
protected
修饰符和private
很类似,除了它所声明的成员可以在它的子类中使用之外。
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); // error
注意我们不能在类外使用protected
成员,但可以在它的子类的方法中调用。
当一个类的构造器被声明为protected
成员时,这意味你不能够在该类之外实例化该类,但是它能够被继承。
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee can extend Person
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");
let john = new Person("John"); // Error: The 'Person' constructor is protected
4、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"; // error! name is readonly.
5、参数属性
我们可以在构造器参数定义的同时定义一个类的成员,只要在参数的前面加上修饰符即可(readonly
,public
,private
,protected
),例如以上例子可以改成如下形式。
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
6、存取器
typescript支持getters/setters
,这给了你控制成员获取和设置的方式。
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) {
console.log(employee.fullName);
}
关于存取器有几点需要注意,首先必须设置编译器输出的是es5及更高版本,es3是不支持存取器的。另外,如果只设置get
而没有设置set
,这个属性就自动被设置为readonly
的。
7、静态属性
在以上案例中我们使用的都是实例属性,我们也可以使用static
关键字设置类的静态成员,通过使用类名.property
来访问类的静态成员,类的静态成员被所有类的实例所共享。
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 scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
8、抽象类
可以使用abstract
关键字声明一个抽象类和抽象方法,和接口不同的是,抽象类可以包含成员的具体实现。被abstract
声明的方法只有函数签名而没有方法体,且必须在子类中实现。另外抽象类不可以实例化。
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // must be implemented in derived classes
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // constructors in derived classes must call super()
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type
9、高级技术
class
其实是一个语法糖,声明一个类实际上创建了两个东西:实例的类型和和一个构造函数。
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
转化后
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
我们也可以使用接口来继承一个类
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};