# TypeScript 中的类使用

TypeScript 中的类使用

学习资料:ts.xcatliu.com/advanced/cl...

传统方法中,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 生成新实例的时候,会自动调用构造函数。

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

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 关键字来调用父类的构造函数和方法。

typescript 复制代码
// 定义为父类
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 可以改变属性的赋值和读取行为:

typescript 复制代码
// 使用 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 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:

typescript 复制代码
// 静态方法 + 静态属性

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 的。

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

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

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

typescript 复制代码
class B {
  name: string 
  constructor(name: string) {
    this.name = name
  }
  p() {
    console.log(this.name)
  }
}

private 私有

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

typescript 复制代码
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 一样是吧?

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

typescript 复制代码
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里面的方法打印一下都不可以:

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

依旧是报错的:

protected 受保护

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

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

typescript 复制代码
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

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

typescript 复制代码
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 定义在参数上面,那么他会创建并且初始化参数

typescript 复制代码
// 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 用于定义抽象类和其中的抽象方法。

什么是抽象类?

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

typescript 复制代码
// 抽象类
abstract class A {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

let a = new A('Jack');

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

上面的例子中,我们定义了一个抽象类 Animal,并且定义了一个抽象方法 sayHi。在实例化抽象类的时候报错了。

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

typescript 复制代码
// 抽象类
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,所以编译报错了。

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

typescript 复制代码
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,编译通过了。

相关推荐
AlienZHOU5 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc8 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师10 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen10 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒11 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow11 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81811 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007811 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明11 小时前
第 6 章 组件进阶
前端·vue.js
江华森11 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端