面向对象
TS
对原有的class
用法进行了更多运行时的功能扩展。
class
可以看作是一个语法糖,新的class
写法只是让对象原型写法更加清晰、更像面向对象编程的语法。
类的定义
使用TS
约束属性并实例化对象。
typescript
class Person {
name: string;
constructor(name: string) {
// 实例属性
this.name = name;
}
// 原型属性
sayHi() {
return `My name is ${this.name}`;
}
}
let p = new Person('Jack');
修饰符
TS
提供了几种语义化的修饰符,用于描述类中各种属性:
readonly
只读属性,无法重新赋值public
表示公有的访问修饰符,在任何地方都可以访问到private
表示私有的访问修饰符,只能在类的内部进行使用protected
:表示受保护的访问修饰符,只能在类的内部及其子类内部使用
typescript
class Person {
// 只读属性
readonly id: string = `#${this.getRandomNum()}`;
// 公共属性
public name: string;
// 私有属性
private age: number;
// 受保护属性
protected gender: "male" | "female" = "male";
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
构造函数参数
通过构造器传递属性参数,并在实例化时给实例属性初始化,这是很常见的写法,访问修饰符可以修饰构造器的参数,可以让实例化赋值这一过程变得更加简单。
typescript
class Person {
// ...
constructor(public name: string, private age: number) {}
}
实际上就是TS
提供的语法糖,简化了初始化过程,修饰了的构造参数已经绑定在this
上了。
静态属性
静态属性绑定在class
的原型链上,也可以搭配修饰符定义,修饰符的位置要在static
关键字的前面。
typescript
class Site {
static url: string = "xxx";
static getSiteInfo() {
return "xxx";
}
}
接口实现
支持类实现一个接口interface
的行为来约束class
的属性和方法,通过implements
关键字实现。
typescript
interface IPerson {
name: string;
age: number;
sayHello(): void;
}
// Person必须要实现IPerson中每个属性约束
class Person implements IPerson {
constructor(public name: string, public age: number) {}
sayHello() {
console.log(`hello, ${this.name}`);
}
}
一个类可以实现多个接口:
typescript
interface IPerson {/**/}
interface IBase {/**/}
class Person implements IPerson, IBase {/**/}
封装 - 私有属性
有了访问修饰符搭配存取器,可以更语义化做到封装。
typescript
class Person {
constructor(private _name: string) {}
get name() {
return this._name;
}
set name(val) {
this._name = val;
}
}
继承
JS
的class
就有继承的能力,通过extends
关键字实现,子类继承父类的属性和方法,并扩展或重写父类的方法。
typescript
class Person{ /*...*/ }
class Student extends Person{
constructor(){
// 实现继承后,如果显式写了构造器函数,则必须调用super方法
super()
}
}
多态
同一方法在不同子类中有不同实现,通过继承或接口实现多态行为。
继承实现多态
子类继承父类,子类重写父类方法。
typescript
class Shape {
public calculateArea(): number {
return 0;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
// 重写父类方法 override 重写关键字
public override calculateArea(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super();
}
// 重写父类方法
public override calculateArea(): number {
return this.width * this.height;
}
}
// 多态调用
// 这里的shapes无论是设置Circle还是Rectangle类型都会报错
// 设置它们的父类Shape才不会报错,这样是多态的表现
const shapes: Shape[] = [new Circle(5), new Rectangle(4, 6)];
shapes.forEach((shape) => {
console.log(shape.calculateArea());
// 输出:78.54(圆面积)和 24(矩形面积)
});
接口实现多态
多个类实现同一个接口,提供不同的实现。
typescript
interface Shape {
calculateArea(): number;
}
class Circle implements Shape {
constructor(public radius: number) {}
// 重写父类方法 override 重写关键字
public calculateArea(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
// 重写父类方法
public calculateArea(): number {
return this.width * this.height;
}
}
// 多态调用
const shapes: Shape[] = [new Circle(5), new Rectangle(4, 6)];
shapes.forEach((shape) => {
console.log(shape.calculateArea());
// 输出:78.54(圆面积)和 24(矩形面积)
});
抽象类 - abstract
抽象类的主要目的是提供一个基类,定义子类必须要遵循的结构和行为。抽象类允许部分实现,这样可以减少代码重复,同时强制子类必须实现这些方法,确保一致性。
抽象的特点:
- 使用
abstract
定义抽象类 - 抽象方法只能出现在抽象类中
- 抽象类不能被实例化,只能被继承
- 可以包含抽象方法和具体实现方法
- 强制子类实现抽象方法
将上面的代码🌰用抽象类进行改造:
typescript
abstract class Shape {
public abstract calculateArea(): number;
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
// 实现抽象方法
public override calculateArea(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super();
}
// 实现抽象方法
public override calculateArea(): number {
return this.width * this.height;
}
}
// const s = new Shape() // Error 不能对抽象类进行实例化
const shapes: Shape[] = [new Circle(5), new Rectangle(4, 6)];
shapes.forEach((shape) => {
console.log(shape.calculateArea());
// 输出:78.54(圆面积)和 24(矩形面积)
});
抽象类和接口的区别:
- 抽象类的目的是子类的共同特性的抽离,接口通常是描述一些行为约束
- 抽象类只能单继承,接口可以多实现
- 抽象类中可以有具体的函数实现,而接口只能声明
- 抽象类表达的是
is
的关系,比如猫是动物,接口更多表达的是has
的关系,比如猫有跑的方法、但是人也有跑的方法,这两者都能实现接口