关键字 class
class Greeter {
greetering: string;
constructor(message: string){
this.greetering = message;
}
// 在使用任何类成员是都使用this关键字
greet() {
return 'Hello, ' + this.greetering;
}
}
let greeter = new Greeter('world');
继承
基于类的程序设计中,一种最基本的模式是允许使用继承来扩展现有的类
- 类从基类中继承了属性和方法
- 派生类手动包含构造函数时,必须调用super(),他会执行基类的构造函数,在构造函数里访问this的属性之前,必须要先调用super(),这是TS强制执行的重要规则
// 基类或者超类
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');
// 即使定义成Animal类型,但是他的值是House,所以调用move时,会调用Horse里重写的方法
let tom: Animal = new Horse('Tommy the Palomino');
sam.move();
tom.move(34);
成员修饰符
- public:定义类成员时如果不加修饰符那么默认为public,可以随意访问类中定义的成员
- private:不能在声明他的类的外部访问
- protected:与private相似,但是protected成员在派生类(子类)中仍然可以访问
- readonly:将属性设置为只读的,只读属性必须在声明时或构造函数里被初始化
// public 类型
class Person2 {
public name: string;
public constructor(theName: string){
this.name = theName;
}
public move(distanceInMeters: number){
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
// private类型
class Person1 {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
let person = new Person1("Book");
person.name; // error name是私有的访问不到
// protected类型
class Person {
protected name: string;
constructor(name: string){
this.name = name;
}
}
class Employ 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 Employ('Howard', 'Sales');
console.log(howard.getElevatorPitch());
console.log(howard.name) // error protected 类型外部无法访问
// 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 = 'Ald'; // error name属性是只读的
console.log(dad.name)
注:构造函数也可以被标记为protected,意味着这个类不能在包含他的类外被实例化,但能被继承
// 构造函数也可以被标记为protected,意味着这个类不能在包含他的类外被实例化,但能被继承
class Person3 {
protected name: string;
protected constructor(theName: string){
this.name = theName;
}
}
class Employee3 extends Person3{
private department: string;
constructor(name: string, department: string){
super(name);
this.department = department;
}
public getElement(){
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let hward = new Employee3('Howrl',"Sales");
let john = new Person3('John'); // error Person3的构造函数是被保护的
描述:TS使用的是结构性类型系统,当比较两种不同类型时,并不在乎他们从何处而来,如果所有成员类型都是兼容的,就认为他们的类型是兼容的
class Animal1 {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Rhino extends Animal1 {
constructor(){
super('Rhino');
}
}
class Employee {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
let animal = new Animal1('Goat');
let rhino = new Rhino();
let employee = new Employee('Bob');
// animal跟rhino共同继承自基类Animal1 所以出处是一样的,所以类型是兼容的
animal = rhino;
// employee是独立的虽然字段一样,但是出处不同,所以TS会认为类型不兼容
animal = employee; // error animal 与 employee不兼容
特殊:当比较带有private或protected成员的类型的时候,如果其中一个类型里包含一个private成员,那么只有当另外一个类型中也存在这样一个private成员,并且他们都来自同一处声明时,才认为这两个类是兼容的,protected同样适用
参数属性
可以方便的在一个地方定义并初始化一个成员
定义:参数属性通过给构造函数参数前面添加一个访问限定符来声明,使用private限定一个参数属性会声明并初始化成一个私有成员,public,protected类似
class Octopus1 {
readonly numberOfLegs: number = 8;
// 直接在构造函数中创建跟初始化name成员,把声明跟赋值合并
constructor(readonly name: string){}
}
存取器
TS通过getters/setters来截取对对象成员的访问,可以有效控制对象成员访问
注意:
- 存取器支持ES5或者更高版本
- 只有带get不带set的存取器自动被推断为readonly
let passcode = 'secret passcode';
class Employee4{
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 employee4 = new Employee4();
employee4.fullName = 'Bob Smith';
if(employee4.fullName){
console.log(employee4.fullName);
}
静态属性
类实例成员:只有在类被实例化的时候才会被初始化,并且通过this.访问实例成员
静态属性:属性存在于类本身上而不是实例上,使用static关键字定义,通过构造函数名.来访问静态成员
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);
let grid2 = new Grid(5.0);
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
抽象类
作为其他派生类的基类使用,一般不会被实例化
注:不同于接口,抽象类可以包含成员的实现细节
关键字:abstract关键字用来定义抽象类或者抽象类内部的抽象方法
抽象类特征:
- 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
- 抽象方法的语法与接口方法相似,两者都是定义方法签名但不包含方法体
- 抽象方法必须包含abstract关键字并且可以包含访问修饰符
// 定义
abstract class Animal4{
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
abstract class Department {
constructor(public name: string){}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor(){
super('Accounting and Auditiing'); // 派生类构造函数必须调用super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let departMent: Department; // 允许创建一个对抽象类型的引用
// departMent = new Department(); // error 不能创建一个抽象类型的实例
departMent = new AccountingDepartment(); // 允许对一个抽象类子类进行实例化跟赋值
departMent.printName();
departMent.printMeeting();
// departMent.generateReports(); // error 方法在声明的抽象类中不存在
构造函数
当在TS中声明了一个类时,实际上同时声明了很多东西:
- 实例的类型
- 创建了一个构造函数,构造函数会在使用new创建类实例的时候被调用,这个构造函数也包含了类的所有静态属性
class Greeter1 {
greeting: string;
constructor(message: string){
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}
// Greeter1类的实例的类型是Greeter1
let greete: Greeter1;
greete = new Greeter1('world');
console.log(greete.greet());
把类当做接口使用:
因为类定义会创建两个东西:类的实例类型、一个构造函数
因为类可以创建类型,所以能够在允许使用接口的地方使用类
class Point {
x: number;
y: number;
}
interface Point3d extends Point{
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
console.log(point3d);