类基础
概述
类(Class)是 TypeScript 中面向对象编程的核心概念。类提供了一种创建对象的模板,定义了对象的属性和方法。通过类,我们可以封装数据和行为,实现代码的复用和组织。TypeScript 的类支持 ES6+ 的类语法,并在此基础上添加了类型注解、访问修饰符等增强特性,使得类的使用更加安全和灵活。
基本语法
类定义
使用 class 关键字定义类,类名通常使用 PascalCase(首字母大写的驼峰命名):
typescript
// 基本类定义
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): void {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
// 创建类的实例
const person = new Person("Alice", 30);
person.greet(); // "Hello, my name is Alice and I am 30 years old."构造函数
构造函数(Constructor)是类的特殊方法,用于初始化类的实例。构造函数使用 constructor 关键字定义,在创建类的实例时自动调用:
typescript
class User {
name: string;
email: string;
// 构造函数
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
// 使用 new 关键字创建实例
const user = new User("John", "john@example.com");
console.log(user.name); // "John"
console.log(user.email); // "john@example.com"提示
每个类只能有一个构造函数。如果不定义构造函数,TypeScript 会提供一个默认的无参构造函数。
实例属性
实例属性是类的成员变量,每个类的实例都有自己独立的属性值:
typescript
class Product {
// 声明实例属性
name: string;
price: number;
inStock: boolean;
constructor(name: string, price: number, inStock: boolean = true) {
// 在构造函数中初始化属性
this.name = name;
this.price = price;
this.inStock = inStock;
}
}
// 创建实例
const product1 = new Product("Laptop", 999, true);
const product2 = new Product("Mouse", 29, false);
// 每个实例有独立的属性值
console.log(product1.name); // "Laptop"
console.log(product2.name); // "Mouse"方法
方法是定义在类中的函数,用于定义对象的行为:
typescript
class Calculator {
// 实例方法
add(a: number, b: number): number {
return a + b;
}
subtract(a: number, b: number): number {
return a - b;
}
multiply(a: number, b: number): number {
return a * b;
}
divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Division by zero is not allowed");
}
return a / b;
}
}
// 使用实例方法
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
console.log(calc.subtract(10, 4)); // 6
console.log(calc.multiply(3, 4)); // 12
console.log(calc.divide(15, 3)); // 5属性初始化
在构造函数中初始化
最常见的属性初始化方式是在构造函数中赋值:
typescript
class Student {
name: string;
age: number;
grade: string;
constructor(name: string, age: number, grade: string) {
this.name = name;
this.age = age;
this.grade = grade;
}
}
const student = new Student("Alice", 18, "A");属性初始化器
TypeScript 支持在声明属性时直接初始化(属性初始化器):
typescript
class Config {
// 使用属性初始化器
apiUrl: string = "https://api.example.com";
timeout: number = 5000;
retries: number = 3;
// 也可以在构造函数中重新赋值
constructor(apiUrl?: string) {
if (apiUrl) {
this.apiUrl = apiUrl;
}
}
}
const config = new Config();
console.log(config.apiUrl); // "https://api.example.com"
console.log(config.timeout); // 5000
const customConfig = new Config("https://custom.api.com");
console.log(customConfig.apiUrl); // "https://custom.api.com"可选属性
属性可以声明为可选,使用 ? 标记:
typescript
class User {
name: string;
email: string;
phone?: string; // 可选属性
constructor(name: string, email: string, phone?: string) {
this.name = name;
this.email = email;
this.phone = phone; // 可选参数
}
getContactInfo(): string {
// 访问可选属性时需要检查
return this.phone
? `${this.name} - ${this.email} - ${this.phone}`
: `${this.name} - ${this.email}`;
}
}
const user1 = new User("Alice", "alice@example.com");
const user2 = new User("Bob", "bob@example.com", "123-456-7890");
console.log(user1.getContactInfo()); // "Alice - alice@example.com"
console.log(user2.getContactInfo()); // "Bob - bob@example.com - 123-456-7890"访问修饰符
TypeScript 提供了访问修饰符来控制类成员的可见性:
public(默认)
public 是默认的访问修饰符,成员可以在任何地方访问:
typescript
class Person {
// public 是默认的,可以省略
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public greet(): void {
console.log(`Hello, I'm ${this.name}`);
}
}
const person = new Person("Alice", 30);
console.log(person.name); // 可以访问
person.greet(); // 可以调用private
private 成员只能在类内部访问:
typescript
class BankAccount {
private balance: number; // 私有属性
constructor(initialBalance: number) {
this.balance = initialBalance;
}
// 公共方法访问私有属性
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
}
}
public withdraw(amount: number): boolean {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
return true;
}
return false;
}
public getBalance(): number {
return this.balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// ❌ 错误:不能直接访问私有属性
// console.log(account.balance); // Error: Property 'balance' is privateprotected
protected 成员可以在类内部和子类中访问:
typescript
class Animal {
protected name: string; // 受保护属性
constructor(name: string) {
this.name = name;
}
protected makeSound(): void {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
// 子类可以访问 protected 成员
public bark(): void {
this.makeSound(); // 可以访问父类的 protected 方法
console.log(`${this.name} barks: Woof!`); // 可以访问父类的 protected 属性
}
}
const dog = new Dog("Buddy");
dog.bark(); // "Buddy makes a sound" "Buddy barks: Woof!"
// ❌ 错误:外部不能访问 protected 成员
// dog.makeSound(); // Error: Property 'makeSound' is protected
// console.log(dog.name); // Error: Property 'name' is protected信息
关于访问修饰符的详细内容,请参考访问修饰符章节。
只读属性
使用 readonly 关键字可以将属性标记为只读,只读属性只能在声明时或构造函数中初始化:
typescript
class User {
readonly id: number;
name: string;
readonly createdAt: Date;
constructor(id: number, name: string) {
this.id = id; // 可以在构造函数中初始化
this.name = name;
this.createdAt = new Date(); // 可以在构造函数中初始化
}
updateName(newName: string): void {
this.name = newName; // ✅ 可以修改普通属性
// ❌ 错误:不能修改只读属性
// this.id = 999; // Error: Cannot assign to 'id' because it is a read-only property
// this.createdAt = new Date(); // Error
}
}
const user = new User(1, "Alice");
console.log(user.id); // 1
console.log(user.createdAt); // 当前日期
// ❌ 错误:不能修改只读属性
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property注意
readonly 只在编译时生效,运行时仍然可以修改。如果需要真正的不可变对象,可以使用 Object.freeze() 或第三方库。
使用示例
示例 1:图书管理系统
typescript
class Book {
private id: number;
public title: string;
public author: string;
public isbn: string;
private available: boolean;
readonly createdAt: Date;
constructor(id: number, title: string, author: string, isbn: string) {
this.id = id;
this.title = title;
this.author = author;
this.isbn = isbn;
this.available = true;
this.createdAt = new Date();
}
// 公共方法访问私有属性
public borrow(): boolean {
if (this.available) {
this.available = false;
return true;
}
return false;
}
public return(): void {
this.available = true;
}
public isAvailable(): boolean {
return this.available;
}
public getInfo(): string {
return `${this.title} by ${this.author} (ISBN: ${this.isbn})`;
}
}
// 使用示例
const book1 = new Book(1, "TypeScript Handbook", "Microsoft", "978-0-123456-78-9");
const book2 = new Book(2, "JavaScript Guide", "Mozilla", "978-0-987654-32-1");
console.log(book1.getInfo()); // "TypeScript Handbook by Microsoft (ISBN: 978-0-123456-78-9)"
if (book1.isAvailable()) {
book1.borrow();
console.log("Book borrowed successfully");
}
book1.return();
console.log(book1.isAvailable()); // true示例 2:购物车系统
typescript
class CartItem {
public productId: number;
public productName: string;
public price: number;
private quantity: number;
constructor(productId: number, productName: string, price: number, quantity: number = 1) {
this.productId = productId;
this.productName = productName;
this.price = price;
this.quantity = quantity;
}
public increaseQuantity(amount: number = 1): void {
if (amount > 0) {
this.quantity += amount;
}
}
public decreaseQuantity(amount: number = 1): void {
if (amount > 0 && this.quantity >= amount) {
this.quantity -= amount;
}
}
public getQuantity(): number {
return this.quantity;
}
public getTotalPrice(): number {
return this.price * this.quantity;
}
}
class ShoppingCart {
private items: CartItem[] = [];
public addItem(item: CartItem): void {
this.items.push(item);
}
public removeItem(productId: number): boolean {
const index = this.items.findIndex(item => item.productId === productId);
if (index > -1) {
this.items.splice(index, 1);
return true;
}
return false;
}
public getTotal(): number {
return this.items.reduce((total, item) => total + item.getTotalPrice(), 0);
}
public getItemCount(): number {
return this.items.length;
}
public clear(): void {
this.items = [];
}
}
// 使用示例
const cart = new ShoppingCart();
const item1 = new CartItem(1, "Laptop", 999, 1);
const item2 = new CartItem(2, "Mouse", 29, 2);
cart.addItem(item1);
cart.addItem(item2);
console.log(`Cart total: $${cart.getTotal()}`); // "Cart total: $1057"
console.log(`Items in cart: ${cart.getItemCount()}`); // "Items in cart: 2"
item2.increaseQuantity(1);
console.log(`Updated total: $${cart.getTotal()}`); // "Updated total: $1086"示例 3:用户认证系统
typescript
class User {
private id: number;
public username: string;
private password: string; // 私有属性,不应该直接访问
public email: string;
private isActive: boolean;
readonly registeredAt: Date;
constructor(id: number, username: string, password: string, email: string) {
this.id = id;
this.username = username;
this.password = password; // 实际应用中应该加密
this.email = email;
this.isActive = true;
this.registeredAt = new Date();
}
// 验证密码(不直接暴露密码)
public verifyPassword(inputPassword: string): boolean {
return this.password === inputPassword; // 实际应用中应该使用哈希比较
}
// 更改密码
public changePassword(oldPassword: string, newPassword: string): boolean {
if (this.verifyPassword(oldPassword)) {
this.password = newPassword; // 实际应用中应该加密
return true;
}
return false;
}
// 激活/停用账户
public activate(): void {
this.isActive = true;
}
public deactivate(): void {
this.isActive = false;
}
public getStatus(): string {
return this.isActive ? "Active" : "Inactive";
}
public getInfo(): string {
return `User: ${this.username} (${this.email}) - Status: ${this.getStatus()}`;
}
}
// 使用示例
const user = new User(1, "alice", "password123", "alice@example.com");
console.log(user.getInfo()); // "User: alice (alice@example.com) - Status: Active"
// 验证密码
if (user.verifyPassword("password123")) {
console.log("Password correct");
}
// 更改密码
if (user.changePassword("password123", "newPassword456")) {
console.log("Password changed successfully");
}
// ❌ 错误:不能直接访问私有属性
// console.log(user.password); // Error: Property 'password' is private
// console.log(user.id); // Error: Property 'id' is private示例 4:任务管理系统
typescript
enum TaskStatus {
Pending = "pending",
InProgress = "in-progress",
Completed = "completed",
Cancelled = "cancelled"
}
class Task {
private id: number;
public title: string;
public description: string;
private status: TaskStatus;
public priority: "low" | "medium" | "high";
readonly createdAt: Date;
private completedAt?: Date;
constructor(
id: number,
title: string,
description: string,
priority: "low" | "medium" | "high" = "medium"
) {
this.id = id;
this.title = title;
this.description = description;
this.status = TaskStatus.Pending;
this.priority = priority;
this.createdAt = new Date();
}
public start(): void {
if (this.status === TaskStatus.Pending) {
this.status = TaskStatus.InProgress;
}
}
public complete(): void {
if (this.status === TaskStatus.InProgress) {
this.status = TaskStatus.Completed;
this.completedAt = new Date();
}
}
public cancel(): void {
if (this.status !== TaskStatus.Completed) {
this.status = TaskStatus.Cancelled;
}
}
public getStatus(): TaskStatus {
return this.status;
}
public getInfo(): string {
return `[${this.status.toUpperCase()}] ${this.title} (Priority: ${this.priority})`;
}
public getDuration(): string | null {
if (this.completedAt) {
const duration = this.completedAt.getTime() - this.createdAt.getTime();
const hours = Math.floor(duration / (1000 * 60 * 60));
const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60));
return `${hours}h ${minutes}m`;
}
return null;
}
}
// 使用示例
const task1 = new Task(1, "Learn TypeScript", "Complete TypeScript tutorial", "high");
const task2 = new Task(2, "Write documentation", "Document the API", "medium");
console.log(task1.getInfo()); // "[PENDING] Learn TypeScript (Priority: high)"
task1.start();
console.log(task1.getStatus()); // TaskStatus.InProgress
task1.complete();
console.log(task1.getInfo()); // "[COMPLETED] Learn TypeScript (Priority: high)"
console.log(task1.getDuration()); // "0h 0m" (或实际耗时)类型检查示例
常见错误
typescript
// ❌ 错误:缺少必需的属性
class Person {
name: string;
age: number;
}
const person = new Person();
// Error: Expected 0 arguments, but got 0.
// 如果定义了构造函数参数,必须提供参数
// ❌ 错误:类型不匹配
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const user = new User("Alice", "30");
// Error: Argument of type 'string' is not assignable to parameter of type 'number'
// ❌ 错误:访问私有成员
class BankAccount {
private balance: number;
constructor(balance: number) {
this.balance = balance;
}
}
const account = new BankAccount(1000);
console.log(account.balance);
// Error: Property 'balance' is private and only accessible within class 'BankAccount'
// ❌ 错误:修改只读属性
class Config {
readonly apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
}
const config = new Config("abc123");
config.apiKey = "new-key";
// Error: Cannot assign to 'apiKey' because it is a read-only property正确写法
typescript
// ✅ 正确:提供所有必需的构造函数参数
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const person = new Person("Alice", 30);
// ✅ 正确:类型匹配
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const user = new User("Alice", 30);
// ✅ 正确:通过公共方法访问私有成员
class BankAccount {
private balance: number;
constructor(balance: number) {
this.balance = balance;
}
public getBalance(): number {
return this.balance;
}
}
const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
// ✅ 正确:只读属性在构造函数中初始化
class Config {
readonly apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
}
const config = new Config("abc123");
console.log(config.apiKey); // "abc123"注意事项
提示
- 类名使用 PascalCase 命名(首字母大写的驼峰命名)
- 使用构造函数初始化实例属性
- 合理使用访问修饰符(public、private、protected)来控制成员的可见性
- 只读属性(readonly)适合用于不会改变的值,如 ID、创建时间等
- 私有属性通常用于封装内部状态,通过公共方法提供访问接口
注意
- 如果不定义构造函数,TypeScript 会提供一个默认的无参构造函数
- 访问修饰符只在编译时生效,运行时仍然可以访问所有成员
readonly只在编译时生效,运行时仍然可以修改(需要使用Object.freeze()实现真正的不可变)- 私有成员(private)在编译后的 JavaScript 中仍然可以访问,TypeScript 的访问控制只在编译时检查
- 类的实例属性必须明确初始化,要么在声明时,要么在构造函数中
重要
- 类是 TypeScript 面向对象编程的基础,理解类对于学习继承、抽象类等高级概念非常重要
- 合理使用访问修饰符可以提高代码的封装性和安全性
- 类的类型检查发生在编译时,不会影响运行时的性能
- 类与接口结合使用,可以实现强大的类型系统
信息
类的优势:
- 封装:将数据和行为组织在一起
- 复用:通过继承实现代码复用
- 类型安全:编译时类型检查
- 组织代码:更好的代码组织结构
类的应用场景:
- 领域模型(User、Product、Order 等)
- 业务逻辑封装
- 数据访问层
- 服务类
- 工具类