Skip to content

面向对象

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;
  }
}

继承

JSclass就有继承的能力,通过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的关系,比如猫有跑的方法、但是人也有跑的方法,这两者都能实现接口

如有转载或 CV 的请标注本站原文地址