TypeScript 类
大白话解释: TypeScript 的类就像"蓝图"。比如"汽车蓝图"规定了汽车有轮子、发动机、方向盘,能跑、能停。类就是对象的蓝图,规定了对象有什么属性、能做什么操作。
为什么要用类?
- 代码复用:写一次蓝图,可以创建无数个对象
- 封装:把数据和操作打包在一起,外部不能随便改
- 继承:子类可以继承父类的属性和方法,不用重复写
- 多态:同一个方法,不同对象有不同的实现
TypeScript 提供了完整的面向对象编程支持,包括类、继承、访问修饰符、抽象类等特性。
类基础
基本类定义
ts
// ---- 基本类:定义对象的"蓝图" ----
// 大白话:就像"月饼模具",用模具可以做出很多月饼
class User {
// 属性声明:定义对象有什么
name: string
age: number
// 构造函数:创建对象时调用,用来初始化属性
constructor(name: string, age: number) {
this.name = name
this.age = age
}
// 方法:定义对象能做什么
greet(): string {
return `你好,我是${this.name},今年${this.age}岁`
}
}
// 使用:用蓝图创建对象
const user = new User('张三', 25)
console.log(user.greet()) // 你好,我是张三,今年25岁
console.log(user.name) // 张三
console.log(user.age) // 25简写构造函数
ts
// ---- 参数属性:在构造函数参数前加修饰符 ----
// 大白话:就像"快捷方式",不用手动写 this.xxx = xxx
class User {
constructor(
public name: string, // 自动创建 public name 属性
public age: number, // 自动创建 public age 属性
private email: string, // 自动创建 private email 属性
readonly id: number // 自动创建 readonly id 属性
) {}
greet(): string {
return `你好,我是${this.name},今年${this.age}岁`
}
}
// 等价于
class User {
name: string
age: number
private email: string
readonly id: number
constructor(name: string, age: number, email: string, id: number) {
this.name = name
this.age = age
this.email = email
this.id = id
}
}
// 使用
const user = new User('张三', 25, '[email protected]', 1)
console.log(user.name) // ✅ 可以访问
// console.log(user.email) // ❌ 错误,私有属性
// user.id = 2 // ❌ 错误,只读属性访问修饰符
public
ts
// ---- public:公共属性,默认修饰符 ----
// 大白话:就像"公开信息",谁都能看、谁都能改
class User {
public name: string
public age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
const user = new User('张三', 25)
console.log(user.name) // ✅ 可以访问
console.log(user.age) // ✅ 可以访问
user.name = '李四' // ✅ 可以修改private
ts
// ---- private:私有属性,只能在类内部访问 ----
// 大白话:就像"隐私信息",只有自己知道
class User {
private password: string
public name: string
constructor(name: string, password: string) {
this.name = name
this.password = password
}
// 公共方法可以访问私有属性
verifyPassword(password: string): boolean {
return this.password === password
}
// 私有方法
private encryptPassword(): string {
return this.password.split('').reverse().join('')
}
}
const user = new User('张三', '123456')
console.log(user.name) // ✅ 可以访问
// console.log(user.password) // ❌ 错误,私有属性
console.log(user.verifyPassword('123456')) // ✅ 通过公共方法访问
// user.encryptPassword() // ❌ 错误,私有方法protected
ts
// ---- protected:受保护属性,类内部和子类可以访问 ----
// 大白话:就像"家族秘密",只有自己和孩子知道
class Animal {
protected name: string
constructor(name: string) {
this.name = name
}
protected makeSound(): void {
console.log('...')
}
}
class Dog extends Animal {
constructor(name: string) {
super(name)
}
bark(): void {
console.log(`${this.name} 汪汪叫`) // ✅ 子类可以访问
this.makeSound() // ✅ 子类可以访问
}
}
const dog = new Dog('旺财')
dog.bark() // ✅ 通过公共方法访问
// console.log(dog.name) // ❌ 错误,受保护属性
// dog.makeSound() // ❌ 错误,受保护方法readonly
ts
// ---- readonly:只读属性,只能在声明时或构造函数中赋值 ----
// 大白话:就像"身份证号",一旦出生就定了,不能改
class User {
readonly id: number
name: string
constructor(id: number, name: string) {
this.id = id // ✅ 构造函数中可以赋值
this.name = name
}
updateName(name: string): void {
this.name = name // ✅ 可以修改
// this.id = 2 // ❌ 错误,只读属性
}
}
const user = new User(1, '张三')
user.name = '李四' // ✅ 可以修改
// user.id = 2 // ❌ 错误,只读属性继承
基本继承
ts
// ---- 继承:子类继承父类 ----
// 大白话:就像"遗传",孩子继承父母的基因
// 基类(父类)
class Animal {
constructor(public name: string) {}
makeSound(): void {
console.log('...')
}
}
// 继承(子类)
class Dog extends Animal {
constructor(name: string) {
super(name) // 调用父类构造函数
}
// 重写方法:子类可以重新实现父类的方法
makeSound(): void {
console.log('汪汪叫')
}
// 新增方法:子类可以有自己的方法
fetch(): void {
console.log(`${this.name} 去捡球`)
}
}
const dog = new Dog('旺财')
dog.makeSound() // 汪汪叫(调用子类的方法)
dog.fetch() // 旺财 去捡球
dog.name // 旺财(继承自父类)多层继承
ts
// ---- 多层继承:爷爷 → 爸爸 → 儿子 ----
// 大白话:就像"家族传承",一代传一代
class Animal {
constructor(public name: string) {}
}
class Mammal extends Animal {
constructor(name: string) {
super(name)
}
breathe(): void {
console.log(`${this.name} 在呼吸`)
}
}
class Dog extends Mammal {
constructor(name: string) {
super(name)
}
bark(): void {
console.log('汪汪叫')
}
}
const dog = new Dog('旺财')
dog.breathe() // 旺财 在呼吸(继承自 Mammal)
dog.bark() // 汪汪叫(自己的方法)
dog.name // 旺财(继承自 Animal)抽象类
抽象类定义
ts
// ---- 抽象类:不能实例化,只能被继承 ----
// 大白话:就像"半成品蓝图",不能直接用,必须补充完整才能用
abstract class Shape {
constructor(public color: string) {}
// 抽象方法:子类必须实现(没有方法体)
abstract getArea(): number
// 普通方法:子类可以直接使用
describe(): string {
return `这是一个${this.color}的图形,面积为${this.getArea()}`
}
}
// 具体类:实现抽象方法
class Circle extends Shape {
constructor(color: string, public radius: number) {
super(color)
}
getArea(): number {
return Math.PI * this.radius ** 2
}
}
class Rectangle extends Shape {
constructor(color: string, public width: number, public height: number) {
super(color)
}
getArea(): number {
return this.width * this.height
}
}
// 使用
const circle = new Circle('红色', 5)
console.log(circle.describe()) // 这是一个红色的图形,面积为78.53981633974483
// const shape = new Shape('蓝色') // ❌ 错误,抽象类不能实例化抽象属性
ts
// ---- 抽象属性:子类必须实现的属性 ----
// 大白话:就像"必填项",子类必须提供这些属性
abstract class Animal {
abstract name: string
abstract readonly legs: number
abstract makeSound(): void
describe(): string {
return `${this.name} 有 ${this.legs} 条腿`
}
}
class Dog extends Animal {
name = '狗' // ✅ 实现抽象属性
legs = 4 // ✅ 实现抽象属性
makeSound(): void {
console.log('汪汪叫')
}
}
const dog = new Dog()
console.log(dog.describe()) // 狗 有 4 条腿静态成员
静态属性和方法
ts
// ---- 静态成员:属于类本身,而不是实例 ----
// 大白话:就像"公司制度",属于公司,不属于某个员工
class MathUtils {
// 静态属性
static PI = 3.14159
// 静态方法
static add(a: number, b: number): number {
return a + b
}
static multiply(a: number, b: number): number {
return a * b
}
}
// 使用:直接通过类名访问
console.log(MathUtils.PI) // 3.14159
console.log(MathUtils.add(1, 2)) // 3
console.log(MathUtils.multiply(2, 3)) // 6
// const math = new MathUtils()
// console.log(math.PI) // ❌ 错误,静态成员不能通过实例访问静态单例模式
ts
// ---- 单例模式:确保一个类只有一个实例 ----
// 大白话:就像"地球",只有一个,大家都用同一个
class Database {
private static instance: Database
private constructor(public host: string, public port: number) {}
static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database('localhost', 3306)
}
return Database.instance
}
connect(): void {
console.log(`连接到 ${this.host}:${this.port}`)
}
}
// 使用
const db1 = Database.getInstance()
const db2 = Database.getInstance()
console.log(db1 === db2) // true,是同一个实例
db1.connect() // 连接到 localhost:3306接口实现
类实现接口
ts
// ---- 接口实现:类必须实现接口定义的所有方法 ----
// 大白话:就像"合同",签了合同就必须按合同办事
// 接口
interface Printable {
print(): void
}
interface Serializable {
serialize(): string
}
// 类实现多个接口
class Document implements Printable, Serializable {
constructor(public title: string, public content: string) {}
print(): void {
console.log(`打印文档:${this.title}`)
console.log(this.content)
}
serialize(): string {
return JSON.stringify({ title: this.title, content: this.content })
}
}
// 使用
const doc = new Document('TypeScript 教程', '内容...')
doc.print()
console.log(doc.serialize())接口约束构造函数
ts
// ---- 接口约束构造函数 ----
// 大白话:就像"工厂必须能生产产品"
interface Constructor<T, A extends any[] = any[]> {
new (...args: A): T
}
function createInstance<T, A extends any[]>(ctor: Constructor<T, A>, ...args: A): T {
return new ctor(...args)
}
class User {
constructor(public name: string) {}
}
const user = createInstance(User, '张三')
console.log(user.name) // 张三方法装饰器
注意: 装饰器有两种语法:
- 新语法(TC39 Stage 3):TypeScript 5.0+ 默认支持,参数是
(value, context)- 旧语法(experimental):需要在 tsconfig.json 中开启
"experimentalDecorators": true下面示例使用旧语法(更常见),如果使用新语法,参数格式不同。
方法装饰器基础
ts
// ---- 方法装饰器:给方法添加额外功能 ----
// 大白话:就像"手机壳",不改变手机本身,但给手机加了新功能
// 旧语法需要在 tsconfig.json 开启:{ "compilerOptions": { "experimentalDecorators": true } }
// 方法装饰器(旧语法)
// 参数说明:
// target: 类的原型对象(不是实例)
// propertyKey: 方法名(如 'add')
// descriptor: 属性描述符,包含原方法
function Log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// 保存原方法
const originalMethod = descriptor.value
// 替换原方法,加上日志功能
descriptor.value = function (...args: any[]) {
console.log(`调用 ${propertyKey},参数:`, args)
// apply(this, args):调用原方法,this 绑定到当前实例
const result = originalMethod.apply(this, args)
console.log(`${propertyKey} 返回:`, result)
return result
}
return descriptor
}
class Calculator {
@Log
add(a: number, b: number): number {
return a + b
}
@Log
multiply(a: number, b: number): number {
return a * b
}
}
const calc = new Calculator()
calc.add(1, 2)
// 调用 add,参数: [1, 2]
// add 返回: 3访问器装饰器
ts
// ---- 访问器装饰器:给 getter/setter 添加额外功能 ----
// 大白话:就像"门卫",在 getter/setter 前后加检查
function Enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value
}
}
class User {
private _name: string
constructor(name: string) {
this._name = name
}
@Enumerable(true)
get name(): string {
return this._name
}
@Enumerable(false)
get displayName(): string {
return `用户:${this._name}`
}
}混入(Mixins)
混入模式
ts
// ---- 混入:给类添加额外功能 ----
// 大白话:就像"贴膜",给手机贴上膜,手机就多了防刮功能
// 为什么用混入?TypeScript 只支持单继承,混入可以在不修改原类的情况下添加功能
// Constructor<T>:一个构造函数类型,T 是实例类型
// new (...args: any[]) => T:表示"能 new 出 T 类型实例的构造函数"
type Constructor<T = {}> = new (...args: any[]) => T
// Timestamped 混入:给任何类添加时间戳功能
// TBase extends Constructor:TBase 必须是一个构造函数
// 返回值:一个继承自 TBase 的新类,增加了 createdAt、updatedAt 属性和 touch 方法
function Timestamped<TBase extends Constructor>(Base: TBase) {
// extends Base:创建一个继承自 Base 的匿名类
return class extends Base {
createdAt = new Date() // 创建时间
updatedAt = new Date() // 更新时间
touch() {
this.updatedAt = new Date() // 更新时间戳
}
}
}
// Activatable 混入:给任何类添加激活/停用功能
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActive = false
activate() {
this.isActive = true
}
deactivate() {
this.isActive = false
}
}
}
// 使用混入
class User {
constructor(public name: string) {}
}
// TimestampedUser:User + 时间戳功能
const TimestampedUser = Timestamped(User)
// ActivatableUser:User + 激活/停用功能
const ActivatableUser = Activatable(User)
// 链式混入:先加 Activatable,再加 Timestamped
// 等价于 Timestamped(Activatable(User))
const TimestampedActivatableUser = Timestamped(Activatable(User))
// 使用
const user = new TimestampedActivatableUser('张三')
console.log(user.createdAt) // Date(来自 Timestamped)
user.activate() // 来自 Activatable
console.log(user.isActive) // true
user.touch() // 来自 Timestamped
console.log(user.updatedAt) // Date实际应用示例
服务类
ts
// ---- API 服务类 ----
// 大白话:就像"快递公司",负责发送和接收请求
class ApiService {
private baseUrl: string
private headers: Record<string, string>
constructor(baseUrl: string, token?: string) {
this.baseUrl = baseUrl
this.headers = {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
}
}
async get<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
headers: this.headers,
})
return response.json()
}
async post<T>(endpoint: string, data: any): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify(data),
})
return response.json()
}
async put<T>(endpoint: string, data: any): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'PUT',
headers: this.headers,
body: JSON.stringify(data),
})
return response.json()
}
async delete<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'DELETE',
headers: this.headers,
})
return response.json()
}
}
// 使用
const api = new ApiService('https://api.example.com', 'token123')
const users = await api.get<User[]>('/users')
const newUser = await api.post<User>('/users', { name: '张三' })事件发射器
ts
// ---- 事件发射器类 ----
// 大白话:就像"广播站",可以发布消息,也可以订阅消息
class EventEmitter<T extends Record<string, any>> {
private listeners: Map<keyof T, Set<Function>> = new Map()
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set())
}
this.listeners.get(event)!.add(listener)
}
off<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
this.listeners.get(event)?.delete(listener)
}
emit<K extends keyof T>(event: K, data: T[K]): void {
this.listeners.get(event)?.forEach(listener => listener(data))
}
}
// 使用
interface UserEvents {
login: { userId: string; timestamp: Date }
logout: { userId: string }
error: { message: string; code: number }
}
const emitter = new EventEmitter<UserEvents>()
emitter.on('login', (data) => {
console.log(`用户 ${data.userId} 登录`)
})
emitter.on('error', (data) => {
console.log(`错误 ${data.code}:${data.message}`)
})
emitter.emit('login', { userId: '123', timestamp: new Date() })通用仓储模式
ts
// ---- 通用仓储模式 ----
// 大白话:就像"仓库管理员",负责管理所有货物的进出
// Entity 接口:所有实体必须有 id
interface Entity {
id: number
}
// T extends Entity:T 必须有 id 属性
class Repository<T extends Entity> {
private items: T[] = []
private nextId = 1
// Omit<T, 'id'>:从 T 中排除 id 属性
// 为什么用 Omit?因为新增时 id 由系统自动生成,不需要用户传
// 例如:User 有 id, name, email,add 时只需要传 { name, email }
add(item: Omit<T, 'id'>): T {
// as T:告诉 TS 合并后的对象是 T 类型
const newItem = { ...item, id: this.nextId++ } as T
this.items.push(newItem)
return newItem
}
findById(id: number): T | undefined {
// find 返回 T | undefined(可能找不到)
return this.items.find(item => item.id === id)
}
findAll(): T[] {
// 返回副本,防止外部直接修改内部数据
return [...this.items]
}
// Partial<T>:T 的所有属性变为可选
// 为什么用 Partial?因为更新时只需要传要改的字段
// 例如:update(1, { name: '李四' }) 只改 name,不改 email
update(id: number, updates: Partial<T>): T | undefined {
const index = this.items.findIndex(item => item.id === id)
if (index === -1) return undefined
// 展开运算符合并:保留原数据,覆盖新数据
this.items[index] = { ...this.items[index], ...updates }
return this.items[index]
}
delete(id: number): boolean {
const index = this.items.findIndex(item => item.id === id)
if (index === -1) return false
this.items.splice(index, 1)
return true
}
}
// 使用
interface User extends Entity {
name: string
email: string
}
const userRepo = new Repository<User>()
// add 的参数是 Omit<User, 'id'> = { name: string; email: string },不需要传 id
const user = userRepo.add({ name: '张三', email: '[email protected]' })
const found = userRepo.findById(1)
// update 的参数是 Partial<User> = { name?: string; email?: string },可以只传部分
userRepo.update(1, { name: '李四' })
userRepo.delete(1)常见问题
类型断言 vs 类型守卫
ts
// 类型断言(不安全)
const user = {} as User
// 类型守卫(安全)
function isUser(obj: any): obj is User {
return obj && typeof obj.name === 'string' && typeof obj.age === 'number'
}访问修饰符选择
ts
// 选择指南:
// - public:默认,任何地方都可以访问
// - private:只在类内部使用,外部不能访问
// - protected:类内部和子类可以访问
// - readonly:只能在声明时或构造函数中赋值抽象类 vs 接口
ts
// 抽象类:
// - 可以有实现代码
// - 可以有构造函数
// - 可以有访问修饰符
// - 只能单继承
// 接口:
// - 只能定义结构,不能有实现
// - 不能有构造函数
// - 所有成员天生公开(不支持访问修饰符)
// - 可以多实现