TypeScript 是面向对象的 JavaScript。类描述了所创建的对象共同的属性和方法。
定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员):
- 属性 − 属性是类里面声明的变量。属性表示对象的有关数据。
- 构造函数 − 类实例化时调用,可以为类的对象分配内存。
- 方法 − 方法为对象要执行的操作。
基本示例
/*
类的基本定义与使用
比如实现一个消息弹框类:
type: 弹框类型
text: 弹框内容
*/
class MsgBox {
/* 声明属性 */
type: string;
text: string;
/* 构造函数 */
constructor(type: string, text: string) {
this.type = type;
this.text = text;
};
/* 方法 */
alertMsg(): void {
console.log(`弹出一个${this.type}类型的弹框,内容是${this.text}`);
};
}
/* 创建类的实例 */
let success = new MsgBox('成功', '提交成功!');
/* 调用实例方法 */
success.alertMsg(); // 弹出一个成功类型的弹框,内容是提交成功!
继承
在 TypeScript 里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
/*
类的继承
brand 品牌
type 类型
price 单价
size 尺寸
*/
class Computer {
brand: string;
type: string;
constructor(b: string, t: string) {
this.brand = b;
this.type = t;
};
getInfo(): void {
console.log(`这是一台${this.brand}的${this.type}电脑`);
};
};
class ComSon extends Computer{
price: number;
size: number;
constructor(s: number, p: number, b: string, t: string) {
/* 继承父类属性 */
super(b, t);
this.price = p;
this.size = s;
this.brand = b;
};
/*
子类中如果出现了和父类同名的方法,则会进行覆盖
也就是调用的时候,调用的就是子类中的方法了!
*/
getInfo(): string {
return `这是一台${this.size}英寸${this.brand}品牌,单价为${this.price}元的${this.type}电脑`;
};
}
let computer1 = new ComSon(16, 6666, '戴尔', '笔记本');
console.log(computer1.getInfo()); // 这是一台16英寸戴尔品牌,单价为6666元的笔记本电脑
访问修饰符
public (共有的,默认为共有的)
在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用 public
来做修饰;例如,C# 要求必须明确地使用 public
指定成员是可见的。 在 TypeScript
里,成员都默认为 public
。
你也可以明确的将一个成员标记成 public
。 我们可以用下面的方式来重写上面的 Computer
类:
private (私有的)
当成员被标记成 private
时,它就不能在声明它的类的外部访问。
protected (受保护的)
protected
修饰符与 private
的行为很相似,但有一点不同,protected
修饰的成员在派生类(子类)中任然可以访问
/*
访问修饰符: 用来描述类内部的属性/方法的可访问性
public: 默认值, 公开的外部也可以访问
protected: 类内部和子类可以访问
private: 只能类内部可以访问
*/
class Computer {
protected brand: string;
private type: string;
constructor(b: string, t: string) {
this.brand = b;
this.type = t;
};
protected getInfo(): void {
console.log(`这是一台${this.brand}的${this.type}电脑`);
};
};
class ComSon extends Computer{
public price: number;
private size: number;
constructor(s: number, p: number, b: string, t: string) {
/* 继承父类属性 */
super(b, t);
this.price = p;
this.size = s;
this.brand = b;
};
public print() {
console.log(this.price); // 6666
console.log(this.type); // 属性“type”为私有属性,只能在类“Computer”中访问。
};
}
let computer1 = new ComSon(16, 6666, '戴尔', '笔记本');
console.log(computer1.getInfo()); // Error 属性“getInfo”受保护,只能在类“Computer”及其子类中访问。
console.log(computer1.size); // Error 属性“size”为私有属性,只能在类“ComSon”中访问。
readonly 修饰符
可以使用 readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Phone {
readonly price: number = 5000;
constructor(price: number) {
this.price = price;
};
}
let iPhone = new Phone(999);
console.log(iPhone.price); // 999
iPhone.price = 100; // Error 无法分配到 "price" ,因为它是只读属性。
参数属性
在上面的例子中,我们必须在 Phone
类里定义一个只读成员 price
和一个参数为 price
的构造函数,并且立刻将 price
的值赋给 this.price
,这种情况经常会遇到。 参数属性可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前 Phone
类的修改版,使用了参数属性:
class Phone {
constructor(readonly price: number) {};
}
let iPhone = new Phone(999);
console.log(iPhone.price); // 999
注意看我们是如何舍弃参数 price
,仅在构造函数里使用 readonly price: number
参数来创建和初始化 price
成员。 我们把声明和赋值合并至一处。
参数属性通过给构造函数参数前面添加一个访问限定符来声明。使用 private
限定一个参数属性会声明并初始化一个私有成员;对于 public
和 protected
来说也是一样。
存取器
TypeScript 支持通过 getters
/setters
来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
class Person {
firstName: string = '温情';
lastName: string = 'key';
get fullName() {
return this.firstName + '-' + this.lastName;
}
set fullName(name) {
const nameArr = name.split('-');
this.firstName = nameArr[0];
this.lastName = nameArr[1];
}
}
const p = new Person();
console.log(p.fullName); // 温情-key
p.firstName = 'KEY';
console.log(p.fullName); // KEY-key
p.fullName = 'key-Ng';
console.log(p.fullName); // key-Ng
静态成员
到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,类的静态成员不是ts
提出的新语法,而是es6
中里面提出的。
在类中通过static
修饰的属性和方法称为静态属性和静态方法,统称为静态成员。
/*
静态属性, 是类对象的属性。即可以用类名直接访问
非静态属性, 是类的实例对象的属性。即通过实例化对象访问
*/
class Person {
static sex = '男' //静态属性
constructor(public name: string) {}
run() { //实例方法
console.log(`${this.name}在运动`)
}
static prin() { //静态方法
console.log(`静态方法`) // 注意这里无法直接使用方法中this.xxx,可以调用Person.xxx
}
}
var p = new Person('温情');
p.run(); // 实例方法调用
// p.prin(); // 属性“prin”在类型“Person”上不存在
console.log(Person.sex); // 静态属性调用
Person.prin(); // 静态方法调用
// Person.run(); // 类型“typeof Person”上不存在属性“run”。
注意:static
不能修饰 constructor
构造函数! 也就是不能用于参数属性
多态 与 抽象类
多态概念: 父类定义一个方法不去实现、让继承它的子类去实现,每一个子类有不同的表现。
抽象类: 抽象类也是一种多态形式,但是抽象类的要求是更加的严格的,抽象类要求如下:
- 使用
abstract
关键字来定义抽象类,抽象方法 - 抽象类是只能是一个基类,也可以理解成是模板,是不能够实例化的
- 抽象类的抽象方法也是一样不会有具体的体现,是给子类去实现
- 抽象类必须至少需要有一个抽象方法,继承抽象类的子类一定要重写抽象方法
总而言之:抽象类就是一个更加严格的多态表现形式
注意: 抽象类里面可以没有抽象方法。但有抽象方法的类必须声明为抽象类
/*
注意:使用多态基础是类的继承或者接口实现
*/
/* Animal 是一个抽象类,里面含有一个eat()抽象方法 */
abstract class Animal {
constructor(public name: string) {};
// 抽象方法 不包含具体实现,要求子类中必须实现此方法
abstract eat(): string;
// 非抽象方法,无需要求子类实现、重写
run() {
return `${this.name}在跑步`;
};
}
class Dog extends Animal {
//子类中必须实现父类抽象方法
eat() {
return `${this.name}吃骨头`
}
}
class Cat extends Animal {
//子类中必须实现父类抽象方法
eat() {
return `${this.name}吃鱼`
}
}
let dog = new Dog('狗');
console.log(dog.eat()); // 狗吃骨头
let cat = new Cat('猫');
console.log(cat.eat()); // 猫吃鱼