TypeScript - 类的使用详解(继承、多态、修饰符、getter/setter、抽象类)
作者:hangge | 2026-08-11 09:17
在早期的 JavaScript(ES5)开发中,需要使用函数和原型链实现类和继承。从 ES6 开始,引入了 class 关键字,我们可以更加方便地定义和使用类。作为 JavaScript 的超集,TypeScript 同样支持使用 class 关键字,并且可以对类的属性和方法等进行静态类型检测。本文将详细介绍 TypeScript 中类的定义、继承、多态、成员修饰符、抽象类等内容。








一、类的定义
1,基本语法
(1)在面向对象编程中,类是描述一切事物的基础,包括特有的属性和方法。TypeScript 中使用 class 关键字来定义类,类内部可以声明各种属性和方法。其特点如下:
- 类内部可以声明各种属性,包括类型声明和初始值设定
- 如果没有类型声明,则默认为 any 类型
- 在默认的 strictPropertyInitialization 模式下,属性必须初始化
- 类可以有自己的构造函数 constructor
- 类可以有自己的函数,这些函数称为方法
(2)下面定义一个简单的 Person 类:
注意:在 TypeScript 中定义类的属性没有初始化值会报错。可以通过在构造函数中初始化属性来解决此问题。
// 定义一个 Person 类
class Person {
// 定义属性,需初始化,否则编译报错
name: string = 'coder';
age: number = 18;
// 定义方法
eating() {
console.log(this.name + " eating");
}
}
// 新建一个类
const p = new Person();
// 访问对象的属性
console.log(p.name, p.age); // 输出: coder 18
// 调用对象的方法
p.eating(); // 输出: coder eating

2,构造函数
上述代码存在缺陷:创建了多个 Person 对象时,每个对象的 name 和 age 初始值都一样。这时可以将属性初始化的过程放到构造函数中:
提示:构造函数不需要返回任何值,它默认返回当前创建的实例。当使用 new 关键字创建实例时,构造函数会被调用。
class Person {
name: string; // 定义属性
age: number;
// 添加构造函数,对属性进行初始化
constructor(name: string, age: number) {
this.name = name; // 属性初始化
this.age = age;
}
eating() {
console.log(this.name + " eating");
}
}
// 传递 name 和 age
const p = new Person("hangge", 18);
console.log(p.name, p.age); // 输出: hangge 18
p.eating(); // 输出: hangge eating

二、类的继承
1,基本继承
(1)面向对象编程中的一个重要特性就是继承。继承不仅可以减少代码量,而且是多态的使用前提。在 TypeScript 中,使用 extends 关键字实现继承,然后在子类中使用 super 访问父类。
提示:子类可以拥有自己的属性和方法,也会继承父类的属性和方法。在子类构造函数中,可以使用 super 关键字调用父类的构造方法。
(2)下面是一个 Student 类继承自 Person 类的示例:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
eating(): void {
console.log("eating");
}
}
// Student 继承 Person
class Student extends Person {
sno: number;
constructor(name: string, age: number, sno: number) {
super(name, age); // super 调用父类的构造函数
this.sno = sno;
}
studying(): void {
console.log("studying");
}
}
const stu = new Student('hangge', 18, 10100);
console.log(stu.name, stu.age); // 访问继承父类的属性
stu.eating(); // 调用继承父类的方法
stu.studying(); // 调用自己的方法

2,方法重写
在实现类的继承时,可以在子类中对父类的方法进行重写。下面样例在 Student 类中定义一个与 Person 类一模一样的 eating 方法,这就属于对方法的重写。如果想调用父类的方法,需要使用 super 关键字。
class Student extends Person {
sno: number;
constructor(name: string, age: number, sno: number) {
super(name, age);
this.sno = sno;
}
// 重写父类的方法
eating() {
console.log("student eating");
super.eating(); // super 调用父类的 eating 方法
}
}
const stu = new Student('hangge', 18, 10100);
stu.eating(); // 调用子类的 eating 方法

三、类的多态
1,多态的概念
(1)面向对象编程中的三大特性是:封装、继承和多态。多态是指:不同的数据类型在进行同一个操作时表现出不同的行为。
(2)下面是一个多态的示例。可以看到,继承是多态的前提。在 makeActions 函数中,接收的 animals 数组包含 Dog 和 Fish 对象,它们都是 Animal 类的子类,即父类的引用指向了子类的对象。当调用 animal.action 方法时,实际上调用的是子类的方法。
class Animal {
action() {
console.log("animal action");
}
}
class Dog extends Animal {
action() { // 子类重写父类的 action 方法
console.log("dog running!!!");
}
}
class Fish extends Animal {
action() {
console.log("fish swimming");
}
}
// 多态是为了写出更具通用性的代码
function makeActions(animals: Animal[]) {
animals.forEach(animal => {
// animals 是父类的引用,指向子类对象
animal.action(); // 调用子类的 action 方法
});
}
makeActions([new Dog(), new Fish()]);
// 输出:
// dog running!!!
// fish swimming

四、成员修饰符
1,三种修饰符
在 TypeScript 中,可以使用三种修饰符来控制类的属性和方法的可见性:
- public:默认的修饰符,表示属性或方法是公有的,可以在类的内部和外部被访问
- private:表示属性或方法是私有的,只能在类的内部被访问,外部无法访问
- protected:表示属性或方法是受保护的,只能在类的内部及其子类中被访问,外部无法访问
提示:使用 private 和 protected 修饰符可以增强类的封装性,避免属性和方法被外部访问和修改。
2,private 修饰符
private 修饰符表示私有属性,只能在类的内部访问:
- 可以看到,私有的 name 属性不能被外界直接访问,需要编写 getName 和 setName 方法来获取和设置 name。
class Person {
// 私有属性不能被外部访问,需要封装方法来操作
private name: string = "coder";
// 默认是 public 方法
getName() {
return this.name; // 获取 name
}
setName(newName: string) {
this.name = newName; // 设置 name
}
}
const p = new Person();
// console.log(p.name); // 错误:直接访问私有的 name 属性会报错
console.log(p.getName()); // ok
p.setName("hangge"); // ok
3,protected 修饰符
protected 修饰符表示受保护的属性,在类内部和子类中可以访问:
class Person {
// protected 的属性,在类内部和子类中可以访问
protected name: string = "123";
}
class Student extends Person {
getName() {
// 子类可以访问父类的 protected 属性
return this.name;
}
}
const stu = new Student();
// console.log(stu.name); // 错误:直接访问受保护的 name 属性会报错
console.log(stu.getName()); // ok
五、只读属性
1,readonly 修饰符
(1)如果不希望外部随意修改某一个属性,可以使用 readonly 修饰符。
注意:readonly 只能保证属性本身不被修改,但如果属性是对象类型,对象内部的属性仍然可以被修改。
(2)通过下面样例可以看到,只读属性在外界是不能被修改的,但是可以在构造函数中赋值。另外,如果只读属性是对象类型,那么对象中的属性是可以修改的。
type FriendType = { name: string };
class Person {
// 只读属性可以在构造函数中赋值,赋值之后就不可以修改
readonly name: string;
// 属性本身不能进行修改,但如果它是对象类型的,则对象中的属性可以修改
readonly friend?: FriendType;
constructor(name: string, friend?: FriendType) {
this.name = name;
this.friend = friend;
}
}
const p = new Person("hangge", { name: 'kobe' });
// 直接修改只读的 name 会报错
// p.name = 'liujun'; // Cannot assign to 'name' because it is a read-only
console.log(p.name, p.friend); // ok
// p.friend = {name: 'evan'}; // 直接修改只读的 friend 会报错
if (p.friend) {
p.friend.name = 'evan'; // friend 对象中的 name 属性是可以修改的
}
六、getter 和 setter
1,访问器
(1)对于一些私有属性,我们不能直接访问,或者对于某些属性,我们想要监听其获取和设置的过程。这时,可以使用 getter 和 setter 访问器。
提示:通过定义 getter 和 setter 访问器,我们可以实现对私有属性的获取和存储的拦截,可以在获取或设置值时添加额外的逻辑。
(2)下面是一个使用 getter 和 setter 的示例:
class Person {
// 私有属性一般习惯以下划线开头命名
private _name: string;
constructor(name: string) {
this._name = name;
}
// setter 访问器
set name(newName: string) {
this._name = newName;
}
// getter 访问器
get name(): string {
return this._name;
}
}
const p = new Person("hangge");
p.name = "coderhangge"; // 调用 setter 访问器为 name 设置值
console.log(p.name); // 调用 getter 访问器获取 name 的值,输出: coderhangge

七、静态成员
1,static 关键字
(1)在类中定义的属性和方法都属于对象级别的。但在开发中,有时也需要定义类级别的属性和方法,也就是类的静态成员。在 TypeScript 中,可以使用关键字 static 来定义类的静态成员:
提示:静态成员属于类本身,而不是类的实例。访问静态成员时,直接使用
类名.静态成员 的方式。(2)下面样例可以看到,这里为 Student 类添加了静态属性 time 和静态方法 attendClass。静态成员通过类名直接访问,不需要创建类的实例。
class Student {
// 定义静态属性
static time: string = "24:00:00";
// 定义静态方法
static attendClass() {
console.log("去学习~");
}
}
// 访问静态属性
console.log(Student.time); // 输出: 24:00:00
// 调用静态方法
Student.attendClass(); // 输出: 去学习~

八、抽象类
1,abstract 关键字
(1)在面向对象编程中,继承和多态是密切相关的。为了定义通用的调用接口,我们通常会让调用者传入父类,通过多态实现更加灵活的调用方式。
- 父类本身可能不需要对某些方法进行具体实现,这时可以将这些方法定义为抽象方法。
- 抽象方法是指没有具体实现的方法。
- 在 TypeScript 中,抽象方法必须存在于抽象类中。
(2)下面使用 abstract 关键字声明一个抽象类 Shape。抽象类 Shape 具有以下特点:
- 抽象类不能被实例化,也就是说,无法通过 new 关键字创建对象
- 抽象类中的抽象方法必须由子类实现,否则该子类必须也是一个抽象类
- 抽象方法使用 abstract 关键字声明,没有方法体
// 抽象类 Shape
abstract class Shape {
// 抽象方法,没有具体实现
abstract getArea(): number;
}
// 继承抽象类
class Rectangle extends Shape {
private width: number;
private height: number;
constructor(width: number, height: number) {
super(); // 在类的继承中,构造函数必须调用 super 函数
this.width = width;
this.height = height;
}
// 实现抽象类中的 getArea 抽象方法
getArea() {
return this.width * this.height;
}
}
class Circle extends Shape {
private r: number;
constructor(r: number) {
super();
this.r = r;
}
// 实现抽象类中的 getArea 抽象方法
getArea() {
return this.r * this.r * 3.14;
}
}
// 多态的应用
function makeArea(shape: Shape) {
return shape.getArea();
}
const rectangle = new Rectangle(20, 30);
const circle = new Circle(10);
console.log(makeArea(rectangle)); // 输出: 600
console.log(makeArea(circle)); // 输出: 314

九、类作为数据类型
1,类类型的用法
(1)类不仅可以用于创建对象,还可以用作一种数据类型。
提示:TypeScript 使用的是结构类型系统(也称为鸭子类型),只要对象具有类定义的所有属性和方法,就可以被视为该类型的实例。
(2)下面样例可以看到,Person 类不仅可以用于创建 Person 对象,还可以用作数据类型。只要对象的结构符合类的定义,就可以被视为该类型。
class Person {
name: string = "coder";
eating() {
console.log("eating");
}
}
// 用类创建对象
const p = new Person();
// 类作为一种数据类型使用
const p1: Person = {
name: "hangge",
eating: function() {}
};
// 类作为数据类型使用
function printPerson(p: Person) {
console.log(p.name);
}
printPerson(new Person());
printPerson({ name: "kobe", eating: function() {} });
全部评论(0)