当前位置: 首页 > news >正文

8. TypeScript 类

TypeScript 中的类是用于创建对象的蓝图。它们允许你对现实世界中的实体进行建模,封装数据,并实现面向对象编程的特性,如继承、多态和方法重写。TypeScript 类提供了静态类型检查,确保属性和方法被正确定义和使用。

一、认识类

TypeScript 类是创建对象的蓝图,它封装了属性(数据)和方法(行为),以促进代码的组织性、可复用性和可读性。

  • 支持继承,允许一个类扩展另一个类并重用其功能。
  • 提供访问修饰符(public、private、protected),用于控制成员的访问权限并实现封装。

例如:

class Person {name: string; // 姓名age: number;  // 年龄constructor(name: string, age: number) {this.name = name; // 初始化姓名this.age = age;   // 初始化年龄}introduce(): string {// 返回一段介绍自己的字符串return `Hi, my name is ${this.name} and I am ${this.age} years old.`;}
}const person1 = new Person("Alice", 25); // 创建一个名为 Alice、年龄为 25 的对象
console.log(person1.introduce()); // 输出介绍信息
  • Person 类定义了 nameage 属性,使用构造函数进行初始化,并包含一个 introduce 方法用于返回问候信息。
  • 通过该类创建了一个对象 person1,并调用 introduce 方法输出个性化的消息。

输出:

Hi, my name is Alice and I am 25 years old.

(一) TypeScript 类的关键组成部分

  • 方法(Methods):在类中定义的函数,用于执行特定操作。
  • 构造函数(Constructors):当从类创建对象时自动调用的特殊方法。构造函数用于初始化类的属性。
  • 属性(Properties):与类的实例相关联的变量。
  • 继承(Inheritance):可以基于已有类创建新类,实现代码复用和功能扩展。

(二) 访问修饰符(Access Modifiers)

  • public:属性和方法可从类外部访问。
  • private:限制只能在类的内部访问。
  • protected:允许在类及其子类中访问。

(三) TypeScript 中的构造函数

构造函数是类中的一个特殊方法,在创建类实例时会自动调用。其主要作用是初始化当前实例的属性。在 TypeScript 中,构造函数允许我们设置对象的初始状态。

class Person {constructor(public name: string, public age: number) {// 初始化属性}
}const john = new Person('Uday', 20);
console.log(`Name: ${john.name}, Age: ${john.age}`);

输出:

Name: Uday, Age: 20

(四) TypeScript 中的对象

对象是类的一个实例,它包含一组键值对。值可以是标量值(如数字、字符串)、函数,甚至是其他对象的数组。

1. 语法

let obj = {key1: value1,key2: value2,// ...
};

2. 注意事项

  • 一般推荐使用点表示法,语法更清晰。
  • 当属性名是变量或者包含特殊字符时,可以使用方括号表示法。

这两种方式都能访问类的属性和方法,取决于使用场景。

// 访问属性
obj.field_name       // 使用点表示法
obj['field_name']    // 使用方括号表示法// 访问方法
obj.function_name()       // 使用点表示法调用方法
obj['function_name']()    // 使用方括号表示法调用方法

3. 简单对象

Person 类具有通过构造函数设置的 nameage 属性。它包含一个 greet 方法,用于输出包含这些详细信息的问候语。嵌套类实例(如 person)可以访问类中定义的属性和方法。

class Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}greet() {console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old.`);}
}const person = new Person('Rahul', 22);
console.log(person.name); // 访问属性
person.greet();           // 访问方法

输出:

Rahul
Hello, I'm Rahul and I'm 22 years old.

(五) 场景示例

1. 管理银行账户

// 定义一个银行账户类
class BankAccount {accountHolder: string; // 账户持有人姓名balance: number;       // 当前账户余额// 构造函数,初始化账户持有人姓名和初始余额constructor(accountHolder: string, initialBalance: number) {this.accountHolder = accountHolder;this.balance = initialBalance;}// 存款方法,将金额加到账户余额上deposit(amount: number): void {this.balance += amount;}// 获取当前余额,返回格式化字符串getBalance(): string {return `账户持有人 ¥{this.accountHolder} 的余额为 $${this.balance}`;}
}// 创建一个银行账户实例,初始余额为 500
const account = new BankAccount("Felix Lu", 500);// 存入 200 元
account.deposit(200);// 输出当前余额
console.log(account.getBalance());
  • BankAccount 类允许创建账户、存入资金和查询余额。
  • account.deposit(200) 会将 200 元添加到初始余额 500 元中,
    getBalance 会显示更新后的账户余额。

输出:

账户持有人 Felix Lu 的余额为 ¥700

2. 表示一个矩形

class Rectangle {width: number;height: number;constructor(width: number, height: number) {this.width = width;this.height = height;}calculateArea(): number {return this.width * this.height;}
}const rect = new Rectangle(10, 5);
console.log(`矩形的面积为: ${rect.calculateArea()}`); 
  • Rectangle 类通过其宽度和高度来计算矩形的面积。
  • 在 rectangle 对象上调用 calculateArea() 方法以计算面积。

输出:

矩形的面积为: 50

二、构造函数参数

TypeScript 中的 ConstructorParameters<Type> 工具类型用于从构造函数类型 Type 中提取其参数类型。它通过确保使用正确的构造函数参数类型来增强类型安全性,从而使函数在创建实例时能够使用构造函数所期望的精确类型。

(一) 语法

type ConstructorParametersType = ConstructorParameters<Type>;

参数说明:

  • ConstructorParametersType:表示构造函数参数类型的类型名。
  • Type:你希望从中提取参数类型的构造函数类型。

(二) 场景示例

1. 提取CourseOrder类的构造函数参数类型

在这个例子中,我们定义了一个名为 CourseOrder 的类,其构造函数接受两个字符串参数。通过使用 ConstructorParameters<typeof CourseOrder>,我们提取了构造函数的参数类型。然后,我们创建了一个 createCourseOrder 函数,利用这些类型来实例化 CourseOrder 对象,并传入不同的参数值。

// 定义一个类及其构造函数
class CourseOrder {constructor(name: string, course: string) {this.name = name;this.course = course;}name: string;course: string;
}// 提取构造函数的参数类型
type CourseOrderConstructorParams = ConstructorParameters<typeof CourseOrder>;// 创建一个函数,用于创建该类的实例
function createCourseOrder(...params: CourseOrderConstructorParams): CourseOrder {return new CourseOrder(...params);
}const instance1 = createCourseOrder("FelixLu", "Java");
const instance2 = createCourseOrder("Raink", "Python");console.log(instance1);
console.log(instance2);

输出:

CourseOrder { name: 'FelixLu', course: 'Java' }
CourseOrder { name: 'Raink', course: 'Python' }

2. 提取 Rectangle 类的构造函数参数类型

在这个示例中,我们创建了一个 Rectangle 类,其构造函数接受两个数字参数:宽度和高度。通过使用 ConstructorParameters<typeof Rectangle>,我们获取了参数类型 [number, number]。然后,我们使用这些类型定义了一个 createRectangle 函数,用于生成具有不同尺寸的 Rectangle 实例。

// 定义一个类及其构造函数
class Rectangle {constructor(public width: number, public height: number) { }
}// 提取构造函数的参数类型
type RectangleConstructorParams =ConstructorParameters<typeof Rectangle>;// 创建一个函数,用于实例化该类
function createRectangle(...params: RectangleConstructorParams): Rectangle {return new Rectangle(...params);
}const rect1 = createRectangle(5, 10);
const rect2 = createRectangle(8, 6);console.log(rect1);
console.log(rect2);

输出:

Rectangle { width: 5, height: 10 }
Rectangle { width: 8, height: 6 }

三、访问修饰符

在 TypeScript 中,访问修饰符用于控制类成员(如属性和方法)的可见性和访问权限,符合面向对象编程中封装和信息隐藏的原则。

  • public(公共):成员可以从任何地方访问;如果未指定访问修饰符,默认即为 public。
  • private(私有):成员只能在定义它的类内部访问。
  • protected(受保护):成员可以在定义它的类及其子类中访问。
class Animal {public name: string;      // 公共属性,可以被外部访问private age: number;      // 私有属性,只能在类内部访问protected species: string; // 受保护属性,可以在子类中访问constructor(name: string, age: number, species: string) {this.name = name;this.age = age;this.species = species;}public getInfo(): string {return `${this.name} 是一只 ${this.species}。`;}// 添加 getAge 方法用于访问私有属性 agepublic getAge(): number {return this.age;}
}class Dog extends Animal {constructor(name: string, age: number) {super(name, age, '狗');}public getDetails(): string {// 通过 getAge 方法访问私有属性 agereturn `${this.name} 是一只 ${this.species},年龄是 ${this.getAge()} 岁。`;}
}const myDog = new Dog('巴迪', 3);
console.log(myDog.name);         // 可访问
console.log(myDog.getInfo());    // 可访问
console.log(myDog.getDetails()); // 可访问
  • name 是 public(公共)的:可以从任何地方访问。
  • age 是 private(私有)的:只能在 Animal 类内部访问。
  • species 是 protected(受保护)的:可以在 Animal 类及其子类 Dog 中访问。

输出:

巴迪
巴迪 是一只 狗。
巴迪 是一只 狗,年龄是 3 岁。

(一) 访问修饰符的类型

1. 公共访问修饰符

public 修饰符允许类的成员在任何地方都可以访问。如果没有指定访问修饰符,所有类成员默认都是 public。

class Animal {public name: string;constructor(name: string) {this.name = name;}public makeSound(): void {console.log(`${this.name} 发出声音。`);}
}const dog = new Animal('京巴');
console.log(dog.name); // 可访问
dog.makeSound(); // 可访问
  • name 和 makeSound 是 public 的,允许从类的外部访问。
  • 我们可以创建 Animal 类的实例,并直接访问它的 name 属性和 makeSound 方法。

输出:

京巴
京巴 发出声音。

2. 私有访问修饰符

private 修饰符限制对类成员的访问,使其只能在定义它们的类内部访问。这确保了封装性,保护了对象的内部状态。

class Person {private ssn: string;constructor(ssn: string) {this.ssn = ssn;}public getSSN(): string {return this.ssn;}
}const person = new Person('123-45-6789');
console.log(person.getSSN());
// console.log(person.ssn); // 这里会报错,因为 ssn 是私有的,不能在类外访问
  • ssn 是私有的,防止在类外部直接访问。
  • 公共方法 getSSN 提供对私有 ssn 属性的受控访问。

输出:

123-45-6789

3. 受保护的访问修饰符(Protected Access Modifier)

protected 关键字用于声明一个类成员,使其可以被定义它的类以及该类的任意子类访问,但不能在类的外部访问。
当你希望某个类的成员可以在其派生类中访问,但不希望被外部直接访问时,这种修饰符非常有用。

class User {protected age: number; // 受保护的属性,只能在类和子类中访问constructor(age: number) {this.age = age;}
}class Employee extends User {public getRetirementAge(): number {return this.age + 65; // 在子类中访问受保护属性}
}const employee = new Employee(30);
console.log(employee.getRetirementAge()); // 输出:95
//console.log(employee.age); // 无法访问受保护属性,编译错误
  • age 是受保护的(protected),这意味着它可以在 User 类以及其子类 Employee 中访问。
  • 尝试从 Employee 的实例中直接访问 age 会导致错误,因为 protected 成员不能在类外部被访问。

输出:

95

(二) 最佳实践

在 TypeScript 中使用访问修饰符的最佳实践如下:

  1. 明确指定访问修饰符:始终为类的成员显式声明 publicprivateprotected,以增强代码的可读性和可维护性。
  2. 封装类成员:使用 privateprotected 来限制对类属性和方法的访问,从而隐藏内部实现细节,提高代码的封装性和安全性。
  3. 从最小可见性开始:优先使用 private 修饰类成员,只有在需要与子类或外部交互时再逐步提升到 protectedpublic,以最大限度地保持封装性。

四、抽象类

在 在 TypeScript 中,抽象类作为其他类的基础模板,不能被直接实例化。

它可以包含没有实现的抽象方法,这些方法必须由任何子类进行具体实现。此外,抽象类还可以包含具有实现的具体方法、属性以及其他成员。

abstract class Animal {abstract makeSound(): void; // 抽象方法,子类必须实现move(): void {console.log('移动中...');}
}class Dog extends Animal {makeSound(): void {console.log('汪汪');}
}const myDog = new Dog();
myDog.makeSound(); // 输出:汪汪
myDog.move();      // 输出:移动中...
  • Animal 类被声明为抽象类,包含一个未实现的抽象方法 makeSound(),以及一个带有实现的具体方法 move()。
  • Dog 类继承自 Animal,并为 makeSound() 方法提供了具体的实现。
  • 创建了 Dog 类的一个实例,并调用了 makeSound() 和 move() 方法,展示了抽象方法与具体方法的使用方式。

输出:

汪汪
移动中...

(一) 抽象类的关键特性

  1. 不能被实例化:抽象类无法直接创建实例,任何试图实例化抽象类的操作都会在编译阶段失败。
  2. 抽象方法:抽象类中可以声明没有实现的方法,称为抽象方法。所有继承该类的子类必须实现这些方法。
  3. 具体方法:抽象类也可以包含完整实现的方法,这些方法可以被子类继承并直接使用。
  4. 可以包含属性和构造函数:尽管抽象类本身不能实例化,但它可以拥有构造函数和属性,通常用于初始化共有属性或设置通用逻辑,供子类继承使用。
  5. 支持多态性:通过抽象类可以实现多态,允许将其子类视作抽象类类型进行操作,增强代码的灵活性。

(二) 抽象类的应用场景

  1. 定义统一接口:抽象类可以作为多个相关类的统一接口,使所有子类遵循统一的结构,提升代码的可读性与维护性。
  2. 代码复用:抽象类中定义的通用方法和属性可以被子类继承,减少代码重复,提高开发效率。
  3. 强制子类实现方法:通过抽象方法,抽象类可以强制子类实现具体的功能,确保不同子类根据自身需求提供不同的实现方式。
  4. 封装共享逻辑:将多个子类之间通用的逻辑集中在抽象类中实现,便于后期集中维护和修改,提升系统的一致性与可扩展性。

(三) 场景示例

1. 带有抽象属性的抽象类

abstract class Person {abstract name: string;display(): void {console.log(this.name);}
}class Employee extends Person {name: string;empCode: number;constructor(name: string, code: number) {super();this.name = name;this.empCode = code;}
}const emp = new Employee('FelixLu', 100);
emp.display();
  • Person 抽象类声明了一个抽象属性 name 和一个具体方法 display(),用于打印 name。
  • Employee 类继承自 Person,实现了 name 属性并添加了 empCode 属性。
  • 创建了一个名为 James、编码为 100 的 Employee 实例,并调用 display() 方法输出 name。

输出:

FelixLu

2. 带有抽象方法的抽象类

abstract class Shape {abstract getArea(): number;printArea(): void {console.log(`面积是 ${this.getArea()}。`);}
}class Circle extends Shape {radius: number;constructor(radius: number) {super();this.radius = radius;}getArea(): number {return Math.PI * this.radius * this.radius;}
}const circle = new Circle(5);
circle.printArea();
  • Shape 抽象类包含一个抽象方法 getArea() 和一个具体方法 printArea(),用于打印面积。
  • Circle 类继承自 Shape,实现了 getArea() 方法来计算圆的面积。
  • 创建了一个半径为 5 的 Circle 实例,并调用 printArea() 方法显示面积。

输出:

The area is 78.53981633974483.

(四) 最佳实践

TypeScript 中使用抽象类的最佳实践:

  1. 使用抽象类封装共享功能:当多个相关类具有共同的行为时,定义抽象类来封装这些共享功能,有助于代码复用和保持一致性。
  2. 为必须实现的方法定义抽象方法:如果所有子类都必须实现某些方法,应在基类中将它们声明为抽象方法,从而强制子类提供具体实现。
  3. 避免直接实例化抽象类:抽象类不应被直接实例化,确保只实例化其子类,以维护设计模式的完整性。

五、继承

继承是面向对象编程(OOP)中的一个基本概念。它允许一个类继承另一个类的属性和方法。继承的类称为子类,被继承属性和方法的类称为父类。继承实现了代码复用,使得一个类可以利用已有类的功能,而无需重新编写代码。

(一) TypeScript中的继承

JavaScript使用的是原型继承,而不是像Java或C++那样的经典继承。TypeScript采用基于类的继承,这其实是对原型继承的语法糖。TypeScript只支持单继承和多级继承。在TypeScript中,类通过使用 extends 关键字来继承另一个类。

1. 语法

class ChildClass extends ParentClass {// 方法和属性
}

2. 例子

在下面这个例子里,类 Car 继承了 Vehicle 类的 honk() 方法。通过这种方式,Car 类可以重用其父类的方法。

class Vehicle {honk(): void {console.log("车辆鸣笛");}
}class Car extends Vehicle {display(): void {console.log("这是一辆汽车");}
}let car = new Car();
car.honk();    // 输出:车辆鸣笛
car.display(); // 输出:这是一辆汽车

输出:

车辆鸣笛
这是一辆汽车

(二) super 关键字

TypeScript 使用 super 关键字调用父类的属性和方法。super 关键字的主要用途包括:

  • 调用父类的构造函数
  • 调用父类的方法

下面举一个 Person 类和继承 Person 类的 Employee 类的例子。

class Person {constructor(private firstName: string, private lastName: string) {this.firstName = firstName;this.lastName = lastName;}getName(): string {return `我是 ${this.firstName} ${this.lastName}.`;}
}
class Employee extends Person {constructor(firstName: string,lastName: string,private jobTitle: string) {// 调用 Person 类的构造函数super(firstName, lastName);}displayInfo(): void {console.log(super.getName()); // 调用父类的方法console.log(`我的职位是 ${this.jobTitle}`);}
}
let employee = new Employee('Felix', 'Lu', 'Web 开发工程师');employee.displayInfo();

这里,Employee 类使用 super() 调用 Person 类的构造函数来初始化 firstName 和 lastName。它还在自身的方法中通过 super 调用了 getName() 方法。

输出:

我是 Felix Lu.
我的职位是 Web 开发工程师

(三) 方法重写

TypeScript 也支持方法重写。我们以 Employee 类为例,该类将重写 Person 类中的方法。

1. 例子

class Person {constructor(private firstName: string, private lastName: string) {this.firstName = firstName;this.lastName = lastName;}displayInfo(): string {return `我是 ${this.firstName} ${this.lastName}。`;}
}class Employee extends Person {constructor(firstName: string,lastName: string,private jobTitle: string) {// 调用 Person 类的构造函数super(firstName, lastName);}displayInfo(): string {return super.displayInfo() + ` 职位是 ${this.jobTitle}。`;}
}let employee = new Employee('Felix', 'Lu', '网页开发工程师');console.log(employee.displayInfo());

输出:

我是 Felix Lu。 职位是 网页开发工程师。

六、方法重写

方法重写是指在派生(子)类中,用与基类(或父类)中同名且签名相同的方法重新定义该方法。这允许子类提供自己对该方法的实现,可以扩展或完全替代基类中定义的行为。

(一) 基本知识

关于方法重写:

  • 子类的方法可以选择使用或不使用父类方法中定义的逻辑。
  • 若要调用基类的方法或属性,可以使用 super 关键字。该关键字帮助子类访问基类的方法或属性。

当我们希望改变子类继承自父类的方法行为时,方法重写尤其有用。

(二) 场景示例

1. 基本的方法重写

在此示例中,我们声明了两个类。在父类中声明了一个方法,随后子类使用自己的逻辑重写了该方法。

class Person {name: string;rollNumber: number;score: number;constructor(name: string, rollNumber: number, score: number) {this.name = name;this.rollNumber = rollNumber;this.score = score;}displayDetails(): void {console.log(`姓名: ${this.name}, 学号: ${this.rollNumber}, 分数: ${this.score}(满分100)`);}
}class Student extends Person {constructor(name: string, rollNumber: number, score: number) {super(name, rollNumber, score);}displayDetails(): void {console.log(`姓名: ${this.name}, 学号: ${this.rollNumber}, 分数: ${this.score}(满分100)`);console.log(`${this.name} 成绩优异...`);}
}const student = new Student('李明', 2, 96);
student.displayDetails();

输出:

姓名: 李明, 学号: 2, 分数: 96(满分100)
李明 成绩优异...

2. 使用 super 调用父类方法

在此示例中,我们在子类重写的方法中使用 super 关键字调用父类的方法,并显示其结果。

class Person {name: string;rollNumber: number;score: number;constructor(name: string, rollNumber: number, score: number) {this.name = name;this.rollNumber = rollNumber;this.score = score;}displayDetails(): void {console.log(`姓名: ${this.name}, 学号: ${this.rollNumber}, 成绩: ${this.score} 分(满分100)`);}
}class Student extends Person {constructor(name: string, rollNumber: number, score: number) {super(name, rollNumber, score);}displayDetails(): void {super.displayDetails();console.log(`${this.name} 是一个聪明的男孩。`);console.log(`${this.name} 成绩很好。`);}
}const student = new Student('FelixLu', 2, 96);
student.displayDetails();

输出:

姓名: FelixLu, 学号: 2, 成绩: 96 分(满分100)
FelixLu 是一个聪明的男孩。
FelixLu 成绩很好。
http://www.xdnf.cn/news/1043119.html

相关文章:

  • Lambda 表达式的语法与使用:更简洁、更灵活的函数式编程!
  • Dina靶机渗透
  • 算法训练第十七天
  • CQF预备知识:Python相关库 -- 通用非均匀随机数抽样 scipy.stats
  • 关于allegro 导入网表报错:Unable to find pin name in问题的解决
  • Java大模型开发入门 (9/15):连接外部世界(中) - 向量嵌入与向量数据库
  • JS进阶 Day03
  • 【构建】Meson、Bazel、Buck现代构建系统
  • RPG28.使用GameplayCue和制作死亡效果
  • Java线程安全计数器实现方案
  • 【stm32f4】ADC实验(stm32hal库)
  • 什么是旋转开关?
  • 使用NVIDIA TensorRT for RTX运行高性能AI应用程序
  • C++线性DP-最优解问题、方案数问题
  • PCL 计算点云的投影密度
  • 【整数递增加法拆分】2022-4-11
  • LangGraph基础知识(Human-in-the-loop)(五)
  • 《甘肃棒垒球》奥运会项目有哪些·垒球1号位
  • vue | async-validator 表单验证库 第三方库安装与使用
  • 高效I/O处理:模型与多路复用的探讨
  • Spring学习笔记
  • (14)python+ selenium自动化测试 -回顾
  • 探索数据的力量:Elasticsearch中指定链表字段的统计查询记录
  • 生日悖论理论及在哈希函数碰撞中的应用
  • AI视野:写作应用AI排行榜Top10 | 2025年05月
  • 隐式时钟与外时钟对比2025.6.14
  • boost之signal的封装及使用例子
  • 数列求和计算
  • XCTF-misc-János-the-Ripper
  • C++斯特林数在C++中的数学理论与计算实现1