# TypeScript 中的类使用

发布时间 2023-12-26 16:18:39作者: 我是ed

TypeScript 中的类使用

学习资料:https://ts.xcatliu.com/advanced/class.html

传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 class

TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。

类的概念

虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。

  • 类(Class):定义了一件事物的抽象特点,包含它的属性和方法 对象(Object):类的实例,通过 new 生成。
  • 面向对象(OOP)的三大特性:封装、继承、多态。
  • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据。
  • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性。
  • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 CatDog 都继承自
    Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat
    方法,程序会自动判断出来应该如何执行 eat
  • 存取器(getter & setter):用以改变属性的读取和赋值行为。
  • 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法。
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现。
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口。

属性和方法

使用 class 定义类,使用 constructor 定义构造函数。

通过 new 生成新实例的时候,会自动调用构造函数。

// 类:描述了所创建的对象共同的属性和方法。
// 通过类可以实例化对象

class Person {
  name: string
  age: number
  // 构造器函数
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  sayHi(str: string) {
    console.log(`hi :${str}`)
  }
}
let p = new Person('我是ed.', 25) // 在new的时候,会执行类中的构造方法。
p.sayHi("我是ed.")

类的继承

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。

// 定义为父类
class Animal {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHi(str: string) {
    console.log("hi " + str + "!")
  }
}

// 定义为子类
class Dog extends Animal {
  constructor(name: string, age: number) {
    // 调用父类的构造函数
    super(name, age);
  }
  sayHi(str: string) {
    console.log("dog " + str + "!")
  }
}

const cat = new Animal("小猫", 10);
cat.sayHi("小猫");

const dog = new Dog("小狗", 5);
dog.sayHi("我是一只小狗")

编译完成,查看运行结果:

在这里插入图片描述

注意,我们可以通过 super.sayHi("狗子") 直接调用父类的方法。

总结:

  • 类与类之间存在继承关系,通过 extends 进行继承。
  • 子类可以调用父类的方法,通过 super
  • 子类可以重写父类的方法。

存取器

使用 gettersetter 可以改变属性的赋值和读取行为:

// 使用 getter 和 setter 可以改变属性的赋值和读取行为
class Name {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    // 设置存取器
    // 读取器 用来读取数据
    get fullName() {
        // 姓名 = 姓氏 + 名字
        return this.firstName + ' ' + this.lastName;
    }
    // 设置器 用来设置数据
    set fullName(name) {
        this.firstName = name;
    }
}
const n = new Name('我是', 'ed.');
console.log(n.fullName);
n.fullName = '我是谁';
console.log(n.fullName);

看一下打印的结果:

在这里插入图片描述

静态成员

静态成员包含静态属性和静态方法,只属于类自己的属性和方法。

使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:

// 静态方法 + 静态属性

class A {
  static name1: string  // 静态属性
  static sayHi() {   // 静态方法
    console.log('hi')
  }
}
A.sayHi()  // 静态方法
const a1 = new A()
a1.sayHi() 

这时候发现,ts 编译器会报错:

在这里插入图片描述

修饰符

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 publicprivateprotected

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
  • private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

public 公有

public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的。

// ** public ** 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
class B {
  public name: string   // 表示公有的属性
  public constructor(name: string) {
    this.name = name
  }
  public p() {   // 表示公有的方法
    console.log(this.name)
  }
}

其实对于 public 而言,写不写都一样,因为不写的话,默认就是 public

所以上面的代码和下面的性质是一模一样的:

class B {
  name: string 
  constructor(name: string) {
    this.name = name
  }
  p() {
    console.log(this.name)
  }
}

private 私有

private 修饰的属性或方法是私有的,不能在声明它的类的外部访问。

class B {
  private name: string 
  constructor(name: string) {
    this.name = name  // name 设置为私有的,只能在类内部访问
  }
  p() {
    console.log(this.name)
  }
}

const b1 = new B('我是ed.')
console.log(b1.name) // 报错
b1.p()

我们声明了私有属性,在外部不能访问,所以说 console.log(b1.name) 会报错,和 java 一样是吧?

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6438004db1264570ab6642965b41c371.png

那现在出现一个问题啊,就是我在类B里面写了一个私有属性,那么如果有一个类C,继承B的话,他可以访问吗?试一下:

class B {
  private name: string 
  constructor(name: string) {
    this.name = name  // name 设置为私有的,只能在类内部访问
  }
  p() {
    console.log(this.name)
  }
}

// const b1 = new B('我是ed.')
// console.log(b1.name) // 报错
// b1.p()


class C extends B{
  constructor(name: string) { 
    super(name)  // 继承父类的属性
  }
}

const c = new C('我是ed.')
console.log(c.name)  // 报错

我们可以看到,报错了:

在这里插入图片描述

所以:子类你可以继承私有属性和私有方法, 但是你不可以访问。

哪怕去类C里面的方法打印一下都不可以:

class C extends B{
  constructor(name: string) { 
    super(name)  // 继承父类的属性
  }
  p() {
    console.log(this.name) // 报错
  }
}

依旧是报错的:

在这里插入图片描述

protected 受保护

protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

上面我们看到了,private 修饰后,子类就算继承了也不可以访问,但是我们想子类可以访问但是外部依旧不能访问,这个时候就需要 protected 了。

class B {
  protected name: string
  constructor(name: string) {
    this.name = name  // name 设置为私有的,只能在类内部访问
  }
  p() {
    console.log(this.name)
  }
}

class C extends B {
  constructor(name: string) {
    super(name)  // 继承父类的属性
  }
  p() {
    console.log(this.name)  // 不报错
  }
}

const c = new C('我是ed.')
console.log(c.name)    // 报错

这个时候呢,子类可以访问但是外部依旧不能访问:

在这里插入图片描述

可以看到,在子类可以继承且访问,但是在外部是不可以访问的。

readonly

只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。

class X {
  readonly age:number  // 表示该属性只读,但是在构造函数中是可以修改的
  constructor(age:number) {
    this.age = age
  }
  update() {
    this.age = 18  // 报错,不能够被修改,因为只读
  }
}

const x = new X(18)
console.log(x.age)
x.age = 20   // 报错,不允许被修改,因为只读

readonly 表示只读,除了在声明类的构造函数中允许修改外,其他位置均不可以修改:

在这里插入图片描述

如果我们的三个 修饰符 和 readonly 定义在参数上面,那么他会创建并且初始化参数

// readonly

class X {
  readonly age:number  // 表示该属性只读,但是在构造函数中是可以修改的
  constructor(readonly age:number) {
    this.age = age
  }
  update() {
    this.age = 18  // 报错,不能够被修改,因为只读
  }
}

const x = new X(18)
console.log(x.age)
x.age = 20   // 报错,不允许被修改,因为只读

看,报错了!

抽象类 abstract

abstract 用于定义抽象类和其中的抽象方法。

什么是抽象类?

首先,抽象类是不允许被实例化的:

// 抽象类
abstract class A {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

let a = new A('Jack');

看,直接实例化一个抽象类,直接报错,告诉我们不允许:

在这里插入图片描述
上面的例子中,我们定义了一个抽象类 Animal,并且定义了一个抽象方法 sayHi。在实例化抽象类的时候报错了。

其次,抽象类中的抽象方法必须被子类实现:

// 抽象类
abstract class A {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

class C extends A {
  public eat() {
    console.log(`${this.name} is eating.`);
  }
}

let c = new C('Tom');

上面的例子中,我们定义了一个类 Cat 继承了抽象类 Animal,但是没有实现抽象方法 sayHi,所以编译报错了。

在这里插入图片描述

下面是一个正确使用抽象类的例子:

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

class Cat extends Animal {
  public sayHi() {
    console.log(`Meow, My name is ${this.name}`);
  }
}

let cat = new Cat('Tom');

上面的例子中,我们实现了抽象方法 sayHi,编译通过了。