面向对象
- 类(class):抽象的概念,是一类事物的抽象,是一类事物的模板,是一类事物的蓝图
- 对象(object):具体的事物,是类的实例,是类的具体表现
- 面向对象(OOP):是一种编程思想,是一种解决问题的思路和方法,有三大特征:封装、继承、多态
类的定义
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
run() {
return `${this.name} is running`;
}
}
上面的代码定义了一个类 Animal
,它包含了两个成员:name
和 run
。其中,name
是一个实例属性,run
是一个实例方法。注意,实例属性必须有初始值或者在构造函数中被初始化。
constructor
是构造函数,它会在类被实例化的时候被调用,用来初始化实例属性。上面的代码中,constructor
接受一个 name
参数,然后把它赋值给 name
实例属性,这个过程是在实例化的时候自动完成的。
我们可以创建一个 Animal
的实例:
const snake = new Animal('lily');
console.log(snake.run()); // lily is running
:::tip ts-node
ts-node 是一个 TypeScript 的执行环境,可以直接在命令行中运行 TypeScript 代码,不需要编译成 js
文件。如果你使用的是 ts-node
,那么你可以直接在命令行中运行上面的代码,不需要编译成 js
文件。实际上是 ts-node
帮我们编译成了 js
文件,然后再执行的。
:::
类的继承
class Dog extends Animal {
bark() {
return `${this.name} is barking`;
}
}
然后调用 Dog
的实例方法:
const xiaobao = new Dog('xiaobao');
console.log(xiaobao.run()); // xiaobao is running
console.log(xiaobao.bark()); // xiaobao is barking
还可以重写构造函数:
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
run() {
return 'Meow, ' + super.run(); // 注意这里的 super
}
}
const maomao = new Cat('maomao');
console.log(maomao.run()); // Meow, maomao is running
在构造函数中,必须使用 super
调用父类的构造函数,否则会报错。这是因为子类实例的创建,是基于父类实例的创建的,如果不调用 super
,子类实例就得不到 this
对象,而 this
对象是实例化类时最为重要的一个环节。
类的修饰符
TypeScript 中有三种修饰符:public
、protected
、private
。
public
public
修饰符是默认的修饰符,可以省略不写。public
修饰符修饰的属性或方法是公有的,可以在任何地方被访问到。
class Animal {
public name: string;
public constructor(name: string) {
this.name = name;
}
public run() {
return `${this.name} is running`;
}
}
const snake = new Animal('lily');
// 更新 name 属性
snake.name = 'lucy'; // 正确
praivate
private
修饰符修饰的属性或方法是私有的,不能在声明它的类的外部访问。在子类中也是不允许访问的。
class Animal {
private name: string;
public constructor(name: string) {
this.name = name;
}
public run() {
return `${this.name} is running`;
}
}
const snake = new Animal('lily');
// 更新 name 属性
snake.name = 'lucy'; // 报错
protected
protected
修饰符修饰的属性或方法是受保护的,它和 private
类似,区别是它在子类中也是允许被访问的。
class Animal {
protected name: string;
public constructor(name: string) {
this.name = name;
}
public run() {
return `${this.name} is running`;
}
}
// 更新 name 属性
snake.name = 'lucy'; // 报错
// 在子类中访问
class Dog extends Animal {
bark() {
return `${this.name} is barking`; // 正确
}
}
const xiaobao = new Dog('xiaobao');
console.log(xiaobao.bark()); // xiaobao is barking
readonly
readonly
修饰符修饰的属性只读,不能被修改。由于 readonly
修饰符修饰的属性是只读的,所以必须在声明时或构造函数中被初始化。被它修饰的属性和方法被称为静态属性和方法。
class Animal {
public readonly name: string;
public constructor(name: string) {
this.name = name;
}
}
const snake = new Animal('lily');
// 更新 name 属性
snake.name = 'lucy'; // 报错
下面是一个静态方法的例子:
class Animal {
public static categories: string[] = ['mammal', 'bird'];
public static isAnimal(a) {
return a instanceof Animal;
}
}
console.log(Animal.categories); // ['mammal', 'bird']
类和接口
接口可以对类的一部分行为进行抽象,只要类实现了接口中的方法,就可以认为它实现了这个接口。
在下面的例子中,car
和 cellphone
都实现了 Radio
接口,所以它们都有 switchRadio
方法。我们可以把 Radio
接口看成是一个规范,只要满足这个规范的类都可以实现这个接口。
class Car{
switchRadio(trigger: boolean) {
// ...
}
}
class Cellphone {
switchRadio(trigger: boolean) {
// ...
}
}
实现 Radio
接口:
interface Radio {
switchRadio(trigger: boolean): void;
}
在 Car
和 Cellphone
类中使用 implements 关键字实现 Radio
接口:
class Car implements Radio {
switchRadio(trigger: boolean) {
// ...
}
}
class Cellphone implements Radio {
switchRadio(trigger: boolean) {
// ...
}
}
备注:如果不实现 Radio
接口中的 switchRadio
方法,那么 Car
类就会报错。
可以实现多个接口,用逗号隔开:
interface Radio {
switchRadio(trigger: boolean): void;
}
interface Battery {
checkBatteryStatus(): void;
}
class cellPhone implements Radio, Battery {
switchRadio(trigger: boolean) {
// ...
}
checkBatteryStatus() {
// ...
}
}
甚至可以将接口继承自另一个接口:
interface Radio {
switchRadio(trigger: boolean): void;
}
interface Battery {
checkBatteryStatus(): void;
}
interface RadioWithBattery extends Radio {
checkBatteryStatus(): void;
}
class cellPhone implements RadioWithBattery { // 这里只用写 RadioWithBattery 接口就可以了
switchRadio(trigger: boolean) {
// ...
}
checkBatteryStatus() {
// ...
}
}