在 Vue 3 的 <script setup>
+ Composition API 风格中,
几乎不用 class
,而是用 interface
、type
、ref
、reactive
等函数式/组合式写法。
但在某些场景下,class
依然有用。
Vue 3 的主流写法是 <script setup>
+ setup()
+ ref/reactive
。
class在vue3/uniapp中,在以下场景还是会用到class:
场景1:定义复杂数据模型(Domain Model)
比如你有一个"用户"类,带方法:
ts
// model/User.ts
export class User {
id: number;
name: string;
email?: string;
constructor(id: number, name: string, email?: string) {
this.id = id;
this.name = name;
this.email = email;
}
// 方法:获取用户简介
getIntro(): string {
return `${this.name} (${this.email || "无邮箱"})`;
}
// 方法:是否是管理员
isAdmin(): boolean {
return this.id === 1;
}
}
然后在组件中使用:
ts
const user = new User(101, '李四', 'lisi@example.com')
console.log(user.getIntro()) // 李四 (lisi@example.com)
用途:封装业务逻辑,比纯对象更强大。
场景2:封装工具类(Utils Class)
ts
// utils/Storage.ts
export class Storage {
static set(key: string, value: any) {
uni.setStorageSync(key, JSON.stringify(value))
}
static get(key: string): any {
const data = uni.getStorageSync(key)
return data ? JSON.parse(data) : null
}
static remove(key: string) {
uni.removeStorageSync(key)
}
}
// 使用
Storage.set('token', 'abc123')
const token = Storage.get('token')
用途:封装本地存储、网络请求、加密等通用功能。
场景3:状态管理中使用class(配合Pinia或自定义Store)
虽然Pinia推荐用defineStore,但你也可以用class管理复杂状态:
ts
// utils/Storage.ts
export class Storage {
static set(key: string, value: any) {
uni.setStorageSync(key, JSON.stringify(value))
}
static get(key: string): any {
const data = uni.getStorageSync(key)
return data ? JSON.parse(data) : null
}
static remove(key: string) {
uni.removeStorageSync(key)
}
}
// 使用
Storage.set('token', 'abc123')
const token = Storage.get('token')
然后在组件中:
ts
import {userStore} from '@/store/UserStore'
userStore.login('admin','123')
场景4:封装网络请求(API Client)
ts
// api/ApiClient.ts
export class ApiClient {
private baseUrl: string
private token?: string
constructor(baseUrl: string) {
this.baseUrl = baseUrl
}
setToken(token: string) {
this.token = token
}
async request<T>(url: string, method: string, data?: any) {
const res = await uni.request({
url: this.baseUrl + url,
method,
data,
header: {
Authorization: this.token ? `Bearer ${this.token}` : ''
}
})
return res.data as T
}
get<T>(url: string) {
return this.request<T>(url, 'GET')
}
post<T>(url: string, data: any) {
return this.request<T>(url, 'POST', data)
}
}
// 使用
const api = new ApiClient('https://api.example.com')
const posts = await api.get<PostListResponse>('/posts')
</script>
这比每次写uni.request更优雅、可复用。
class在Vue3.0/Uniapp中的定位
- 封装业务逻辑(如User) class+方法很适合
- 工具类(Storage/Utils)静态方法方便调用
- API请求客户端(封装baseUrl、token、错误处理)
- 状态管理(复杂逻辑) 比纯对象更易维护
组件用setup,模型用class,数据用interface,工具用static。
UserManager class 案例:
用户登录/登出 本地存储(持久化) 自动刷新Token(模拟) 权限判断 登录状态监听 TypeScript类+静态单例模式
场景:用户管理系统:用户登录后保存信息和 token,能判断是否登录、是否是管理员、自动续期 token。
src/utils/UserManager.ts
1.定义User接口(数据结构)
ts
// types/User.ts 或直接写在 UserManager.ts 顶部
export interface User {
id: number;
name: string;
email: string;
avatar?: string;
role: 'user' | 'admin'; // 角色
}
2.编写UserManager类(核心)
ts
// utils/UserManager.ts
import type { User } from '@/types/User'; // 引入类型
// 模拟 API 延迟
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
export class UserManager {
private user: User | null = null;
private token: string | null = null;
private refreshToken: string | null = null;
private expiresAt: number = 0; // token 过期时间戳
// 监听器列表(用于通知组件状态变化)
private listeners: Array<(user: User | null) => void> = [];
// 私有构造函数(单例模式)
private constructor() {
// 初始化时从本地恢复登录状态
this.restoreSession();
}
// 静态实例(全局唯一)
private static instance: UserManager;
// 获取单例实例
public static getInstance(): UserManager {
if (!UserManager.instance) {
UserManager.instance = new UserManager();
}
return UserManager.instance;
}
// ============ 核心方法 ============
/**
* 登录
*/
async login(username: string, password: string): Promise<{ success: true } | { success: false; message: string }> {
await sleep(800); // 模拟网络延迟
// 简单模拟验证(实际应调用 API)
if (username === 'admin' && password === '123') {
const user: User = {
id: 1,
name: '管理员',
email: 'admin@example.com',
avatar: 'https://i.pravatar.cc/150?u=admin',
role: 'admin'
};
this.setSession(user, 'fake-jwt-token', 'fake-refresh-token', 30 * 60); // 30分钟过期
return { success: true };
} else if (username === 'user' && password === '123') {
const user: User = {
id: 2,
name: '普通用户',
email: 'user@example.com',
avatar: 'https://i.pravatar.cc/150?u=user',
role: 'user'
};
this.setSession(user, 'fake-user-token', 'fake-user-refresh', 30 * 60);
return { success: true };
} else {
return { success: false, message: '用户名或密码错误' };
}
}
/**
* 登出
*/
logout() {
this.user = null;
this.token = null;
this.refreshToken = null;
this.expiresAt = 0;
uni.removeStorageSync('user_session');
this.notifyListeners(null);
}
/**
* 刷新 Token(模拟)
*/
async refreshTokenIfNeeded(): Promise<boolean> {
if (this.token && this.expiresAt < Date.now()) {
await sleep(500);
// 模拟刷新
this.token = this.token + '-refreshed';
this.expiresAt = Date.now() + 30 * 60 * 1000;
this.saveSession();
return true;
}
return false;
}
// ============ 工具方法 ============
/**
* 设置会话
*/
private setSession(user: User, token: string, refreshToken: string, expiresIn: number) {
this.user = user;
this.token = token;
this.refreshToken = refreshToken;
this.expiresAt = Date.now() + expiresIn * 1000;
this.saveSession();
this.notifyListeners(user);
}
/**
* 保存会话到本地
*/
private saveSession() {
const session = {
user: this.user,
token: this.token,
refreshToken: this.refreshToken,
expiresAt: this.expiresAt
};
uni.setStorageSync('user_session', JSON.stringify(session));
}
/**
* 从本地恢复会话
*/
private restoreSession() {
try {
const sessionStr = uni.getStorageSync('user_session');
if (!sessionStr) return;
const session = JSON.parse(sessionStr);
if (session.expiresAt > Date.now()) {
this.user = session.user;
this.token = session.token;
this.refreshToken = session.refreshToken;
this.expiresAt = session.expiresAt;
} else {
// 过期了就清空
uni.removeStorageSync('user_session');
}
} catch (e) {
console.error('恢复会话失败', e);
}
}
/**
* 通知所有监听器
*/
private notifyListeners(user: User | null) {
this.listeners.forEach(fn => fn(user));
}
// ============ 获取状态的方法 ============
/**
* 获取当前用户
*/
getUser(): User | null {
return this.user;
}
/**
* 获取 Token
*/
getToken(): string | null {
return this.token;
}
/**
* 是否已登录
*/
isLoggedIn(): boolean {
return !!this.user && this.expiresAt > Date.now();
}
/**
* 是否是管理员
*/
isAdmin(): boolean {
return this.user?.role === 'admin';
}
/**
* 添加状态监听器(比如组件想监听登录状态变化)
*/
onStatusChange(callback: (user: User | null) => void) {
this.listeners.push(callback);
// 立即触发一次当前状态
callback(this.user);
// 返回取消订阅函数
return () => {
this.listeners = this.listeners.filter(fn => fn !== callback);
};
}
}
在组件中使用
index.vue
<template>
<div>
<h2>用户管理系统</h2>
<div v-if="!userManager.isLoggedIn()">
<p>当前未登录</p>
<button @click="handleLogin" :disabled="loading">
{{ loading ? "登录中..." : "以管理员登录" }}
</button>
</div>
<div v-else>
<p>你好,{{ currentUser?.name }}!</p>
<p>角色:{{ currentUser?.role }}</p>
<p>邮箱:{{ currentUser?.email }}</p>
<button @click="handleLogout">登出</button>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { UserManager } from "@/utils/UserManager";
// 获取单例
const userManager = UserManager.getInstance();
// 响应式数据
const currentUser = ref<User | null>(null);
const loading = ref(false);
// 登录方法
const handleLogin = async () => {
loading.value = true;
const result = await userManager.login("admin", "123");
if (result.success) {
console.log("登录成功");
} else {
console.error(result.message);
}
loading.value = false;
};
// 登出
const handleLogout = () => {
userManager.logout();
};
// 监听登录状态变化
onMounted(() => {
// 添加监听器
const unsubscribe = userManager.onStatusChange((user) => {
currentUser.value = user;
});
// 组件销毁时取消监听(可选,一般不需要)
// onUnmounted(unsubscribe);
});
</script>
正题:当你看完上述的这几个案例,现在我们来看类的使用例子:
ts
// class就是定义类的关键字
//Greeter这个是类的名称,一般就是大驼峰命名如:PascalCase。
//greeting: string;
// greeting是类的实例属性,或称为成员变量。每个通过new Greetter(...)创建的对象都会拥有属于自己独立的greeting属性。
// : string 这是ts的类型注解,明确了greeting属性的类型必须是string,ts的核心之一,检查属性,提供了安全性。
//constructor这是类的构造函数。
//message: string 这是构造函数的参数。它会接收一个string类型的 参数message,
// 当你调用new Greeter("world")时,"world"这个字符就会作为message参数传递给构造函数。
class Greeter {
greeting: string; // 属性声明 (修正了拼写: gerrting -> greeting)
constructor(message: string) { // 构造函数
this.greeting = message; // 赋值!this.greeting = message;作用是将传入的message参数的值赋给当前实例的greeting属性。这是初始化实例属性的关键步骤。
}
greet() { // 方法或者称为成员函数。它定义了Greeter类的实例可以执行的操作。
return "Hello, " + this.greeting; // 这个方法的逻辑:返回一个字符串,该字符串由"hello"和当前实例的greeting属性值拼接而成。
// this.greeting;确保了它访问的是调用该方法的那个特定实例的greeting值。
}
}
let greeter = new Greeter("world"); // 创建实例(这就是实例化的过程。)
// new关键字告诉js/ts引擎要创建一个Greeter类的新对象(实例)。
// Greeter("world")表示调用Greeter类的构造函数,并将"world"作为参数传递。
// 构造函数执行this.greeter=message;将新创建的实例的greeting属性设置为"world"
// let greeter = ...: 这行代码将新创建的 Greeter 实例赋值给一个名为 greeter 的变量。现在,greeter 就是一个具体的 Greeter 对象。
类的继承
类从基类中继承了属性和方法。这里,Dog是一个派生类,它派生自Animal基类,通过extends关键字。派生类通常被称作子类,基类通常被称作超类。
ts
// class就是定义类的关键字
// Animal就是类的名称
// move是Animal类中的方法
// distanceInMeters: number方法中的参数
// = 0是默认参数值,如果调用move这个方法时没有提供参数,distanceInMeters的值将自动设为0.
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
//extends这是继承的关系 Dog继承了Animal类。
// Dog类自动获得了Animal类中定义的所有公告属性和方法(在这个例子中就是move方法)。
// Dog类被称为Animal类的子类(或派生类)。
// Dog可以在继承的基础上添加自己特有的属性和方法。
// bark() { ... }这是Dog类自己定义的一个特有方法。它代表了狗这种动物特有的行为:叫(bark)
// 调用了bark()会打印出来'Woof! Woof!'。
// 重要的是Animal类的实例不能调用bark()方法,因为bark是Dog类特有的。
class Dog extends Animal {
bark() {
console.log('Woof! Woof!');
}
}
// const dog = new Dog();使用new关键字创建Dog类的一个新实例(对象),并将其赋值给常量dog.
// 这个dog实例:
// 拥有从Animal继承来的move方法,拥有Dog类自己定义的bark方法。
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
// Woof! Woof!
// Animal moved 10m.
// Woof! Woof!
ts
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal { //继承 Animal
constructor(name: string) { super(name); } //构造函数
move(distanceInMeters = 5) { //方法重写
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal { //继承 Animal
constructor(name: string) { super(name); } //构造函数
move(distanceInMeters = 45) { //方法重写
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python"); //创建一个snake的实例
let tom: Animal = new Horse("Tommy the Palomino"); //创建一个Horse的实例
sam.move();
tom.move(34);
// Sammy the Python moved 5m.
// Tommy the Palomino moved 34m.
公共,私有与受保护的修饰符。
默认为public
ts里,成员都默认为public。 c#要求必须明确地使用public指定成员是可见的。
ts
//class 类的关键词
//Animal类的名称
//public这是访问修饰符。public表示这个成员(属性或方法)是公开的,可以从类的内部和外部(通过实例)自由访问。
//public name: string;公开的名称信息和名称属性。
//public constructor(theName: string) { this.name = theName; }公平的构造函数,constructor这是类的构造函数,当使用 new Animal(...) 创建类的新实例时,构造函数会自动执行。
//this.name = theName;构造函数的方法体(代码体)。
// public move(distanceInMeters: number) 表示公开的move
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
// 1. 创建实例 (使用 new 关键字和构造函数)
let lion = new Animal("Lion"); // 构造函数被调用,theName = "Lion", lion.name 被设为 "Lion"
// 2. 访问公开属性 (因为 name 是 public)
console.log(lion.name); // 输出: Lion
// 3. 调用公开方法 (因为 move 是 public)
lion.move(15); // 输出: Lion moved 15m.
// 4. 修改公开属性 (因为 name 是 public)
lion.name = "Tiger";
lion.move(5); // 输出: Tiger moved 5m.
理解private 私有的
当成员被标记成private时,它就不能在申明它的类的外部访问。
ts
//private成员是"私有的":他们就行是类的"内部秘密"或"内部工作细节"。
//只能在"类"的内部使用:你只能在定义这个private成员的那个类的内部代码
//(比如其他方法或构造函数)中访问它。
//不能在类的"外部"使用:一旦你创建了这个类的实力(对象),
//帮你就不能通过这个实例在类的外部直接读取或修改private的属性,也不能直接调用private的方法。
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 错误: 'name' 是私有的.
ts
//定义了一个Animal类,包含了一个私有属性name
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
//Rhino类继承了Animal,所以它拥有Animal的结构,包括那个private name属性。
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
// Employee类也有一个同名的私有属性name,看起来和Animal很相似。
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
// 变量声明与复制
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
// ts使用结构化类型系统,意思就是两个类型如果有相同的结构,就可以互相赋值。
// 但有一个重要的例外,当类包含private成员时,他们只会在来自同一个基类(或声明位置相同)时才被认为是兼容的。
// "Animal 的 private name" 和 "Employee 的 private name" 是不同来源的私有成员,因此 Animal 和 Employee 结构不兼容。
animal = employee; // 错误: Animal 与 Employee 不兼容.
理解protected 受保护的
protected成员在派生类中仍然可以访问。
ts
// protected 的含义是成员可以在类内部以及其子类中访问,但不能在类的实例外部访问。
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
//Employee 继承了 Person
class Employee extends Person {
// department 是 private私有的,意味着它只能在 Employee 类内部访问。
private department: string;
constructor(name: string, department: string) {
super(name)
this.department = department;
}
// getElevatorPitch() 方法是 public 的,可以在任何地方调用。
// this.name是protected允许的,而Employee是Person的子类,所以可以访问父类的this.name;
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
// 创建了一个 Employee 的实例 howard。
let howard = new Employee("Howard", "Sales");
// 调用 public 方法,输出:Hello, my name is Howard and I work in Sales.
console.log(howard.getElevatorPitch());
// howard 是一个 实例对象,而不是类内部。
console.log(howard.name); // 错误
构造函数也可以被标记为被保护的protected.这意味着这个类不能包含它的类外被实例化,但是能被继承。
ts
//Person类
//protected受保护的name
//protected受保护的构造函数constructor 将传入的参数赋值给实例的name属性。
// constructor构造函数名称 theName: string参数列表 theName是参数名,string是类型。
// { this.name = theName; }函数体,将参数赋值给当前实例的name属性。
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee 能够继承 Person
// 私有的department属性类型是string
//constructor的 super(name);子类可以调用父类的protected受保护可以访问父类的构造函数
//public公共的 getElevatorPitch方法 ,可以在任何地方调用。
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.
只读的readonly 修饰符
只读属性必须在声明时或构造函数里被初始化。
ts
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.
参数属性
ts
//参数属性通过给构造函数参数前面添加一个访问限定符来声明。 使用 `private`限定一个
//参数属性会声明并初始化一个私有成员;对于 `public`和 `protected`来说也是一样。
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
存取器 TypeScript支持通过getters/setters来截取对对象成员的访问。
将一个简单的类改写成使用get和set。
ts
// 定义一个变量 密码
let passcode = "secret passcode";
//定义Employee类
// private 私有的属性 _fullName ,string类型
class Employee {
private _fullName: string;
// get fullName(): string 这是一个getter(取值器)
// 当你尝试读取 employee.fullName 时,这个方法会被自动调用。它返回内部的 _fullName 值。
// 外部看起来像是在访问一个属性,实际上是调用一个方法。
get fullName(): string {
return this._fullName;
}
//set fullName(newName: string) 这是一个setter(赋值器)
// 当你尝试给 employee.fullName 赋值时,这个方法会被自动调用。
// 它会检查 passcode 是否正确:
// 如果密码正确(等于 "secret passcode"),就允许修改 _fullName。
// 否则,打印错误信息,拒绝修改。
set fullName(newName: string) {
if (passcode && passcode == "secret passcode") {
this._fullName = newName;
} else {
console.log("Error: Unauthorized update of employee!");
}
}
}
// 创建一个 Employee 实例。
let employee = new Employee();
// 这行代码触发 setter。
// 此时 passcode 是 "secret passcode",条件成立:
employee.fullName = "Bob Smith";
// employee.fullName 触发 getter,返回 "Bob Smith"。
// "Bob Smith" 是"真值"(truthy),所以 if 条件成立。
// 执行 alert("Bob Smith");,弹出对话框显示姓名。
if (employee.fullName) {
alert(employee.fullName);
}
静态属性
ts
// 定义一个表示"网格"或"坐标系"的类
class Grid {
// 静态属性:原点坐标,属于类本身,所有实例共享
// 所有 Grid 实例都使用同一个原点 (0, 0)
static origin = {x: 0, y: 0};
// 实例方法:计算给定点到原点的距离
// 参数 point:一个包含 x 和 y 坐标的对象
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
// 计算横向距离(点的 x 坐标 - 原点 x 坐标)
let xDist = (point.x - Grid.origin.x);
// 计算纵向距离(点的 y 坐标 - 原点 y 坐标)
let yDist = (point.y - Grid.origin.y);
// 使用勾股定理计算欧几里得距离(直线距离)
// Math.sqrt(xDist² + yDist²) 得到原始距离
// 然后除以 this.scale,表示"缩放后的距离"
// 注意:这里的除法可能用于模拟不同单位(如像素转厘米)或分辨率
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
// 构造函数:接收一个 scale 参数
// public scale: number 表示:
// 1. 声明一个公共的实例属性 `scale`
// 2. 在创建实例时自动赋值
// 相当于:
// public scale: number;
// constructor(scale: number) { this.scale = scale; }
constructor(public scale: number) { }
}
// 创建两个 Grid 实例,具有不同的缩放因子
let grid1 = new Grid(1.0); // 1x 缩放:距离不缩放
let grid2 = new Grid(5.0); // 5x 缩放:距离除以 5
// 计算点 (10,10) 到原点的距离,并输出
// grid1: 原始距离 ≈14.142,除以 1 → 输出 ≈14.142
console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
// grid2: 原始距离 ≈14.142,除以 5 → 输出 ≈2.828
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
抽象类
abstract
关键字是用于定义收藏类和抽象类内部定义抽象方法。
ts
//abstract表示这是一个抽象类
abstract class Animal {
// abstract makeSound(): void;这是一个抽象方法;
//(): void 表示:没有参数没有返回值(或返回 undefined)
abstract makeSound(): void;
// move这是一个普通方法(具体方法)
move(): void {
console.log('roaming the earch...');
}
}
ts
// 抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
// 抽象方法的语法与接口方法相似。
// 两者都是定义方法签名但不包含方法体。
// 然而,抽象方法必须包含 abstract关键字并且可以包含访问修饰符。
//定义一个Department的抽象类
abstract class Department {
//构造函数:接收一个 name 参数,并自动创建公共属性 this.name
constructor(public name: string) {
}
// 具体方法:打印部门名称
// 所有子类都可以继承并使用这个方法
printName(): void {
console.log('Department name: ' + this.name);
}
// 抽象方法:必须在子类中实现
// 当前类不提供具体实现,只是"声明"这个方法必须存在
abstract printMeeting(): void; // 必须在派生类中实现
}
//定义一个子类AccountingDepartment继承Department
class AccountingDepartment extends Department {
// 子类构造函数
constructor() {
// 必须先调用 super(),才能使用 this
// super() 调用父类构造函数,传入具体的部门名称
super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
}
// 实现父类的抽象方法
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
// 子类特有的方法:生成报告
// 这个方法在父类 Department 中不存在
generateReports(): void {
console.log('Generating accounting reports...');
}
}
// 声明一个变量,类型为 Department(抽象类)
let department: Department;
// 错误!不能实例化抽象类
department = new Department();
// 编译错误:Cannot create an instance of an abstract class.
// 正确!可以实例化具体的子类,并赋值给抽象类型的变量
department = new AccountingDepartment();
// 正确!printName() 在 Department 中定义,可以调用
department.printName();
// 输出: Department name: Accounting and Auditing
// 正确!printMeeting() 是抽象方法,但 AccountingDepartment 已实现
department.printMeeting();
// 输出: The Accounting Department meets each Monday at 10am.
// 错误!generateReports() 不在 Department 类中声明
department.generateReports();
// 编译错误:Property 'generateReports' does not exist on type 'Department'.
高级技巧
构造函数
ts
// 创建一个 Greeter 类
class Greeter {
// 声明一个实例属性:greeting,类型为 string
greeting: string;
// 构造函数:接收一个参数 message(类型为 string)
// 在创建实例时被调用,用于初始化 this.greeting
constructor(message: string) {
this.greeting = message; // 把传入的 message 赋值给 this.greeting
}
// 实例方法:greet()
// 返回一个字符串:"Hello, " + 当前实例的 greeting 属性值
greet() {
return "Hello, " + this.greeting;
}
}
// 声明一个变量 greeter,类型是 Greeter 类
let greeter: Greeter;
// 创建 Greeter 类的一个实例,并传入参数 "world"
// 此时 constructor 被调用,this.greeting = "world"
greeter = new Greeter("world");
// 调用实例的 greet() 方法,并将返回值打印到控制台
console.log(greeter.greet());
ts
// 定义一个名为 Greeter 的类
class Greeter {
// 静态属性:standardGreeting
// 属于类本身,所有实例共享
// 默认问候语,当实例没有设置 greeting 时使用
static standardGreeting = "Hello, there";
// 实例属性:greeting
// 每个 Greeter 实例可以有自己的 greeting 值
// 类型为 string
greeting: string;
// 实例方法:greet()
// 根据当前实例的 greeting 值返回不同的问候语
greet() {
// 判断当前实例是否有 greeting 属性值
if (this.greeting) {
// 如果有 greeting,返回 "Hello, " + greeting
return "Hello, " + this.greeting;
} else {
// 如果没有 greeting(即 undefined 或空字符串)
// 返回类的静态默认问候语
return Greeter.standardGreeting;
}
}
}
// 声明一个变量 greeter1,类型为 Greeter
let greeter1: Greeter;
// 创建 Greeter 的实例,但没有传入 greeting
// 此时 this.greeting 为 undefined
greeter1 = new Greeter();
// 调用 greet() 方法
// 因为 this.greeting 不存在,所以返回静态属性 Greeter.standardGreeting
console.log(greeter1.greet()); // 输出: Hello, there
// 声明一个变量 greeterMaker,类型为 "Greeter 类本身"
// typeof Greeter 表示类的类型(构造函数的类型),而不是实例的类型
let greeterMaker: typeof Greeter = Greeter;
// 通过 greeterMaker 修改静态属性 standardGreeting
// 因为 greeterMaker 指向 Greeter 类,所以这等价于:
// Greeter.standardGreeting = "Hey there!";
greeterMaker.standardGreeting = "Hey there!";
// 创建一个新的实例 greeter2
// 使用 greeterMaker 作为构造函数(等价于 new Greeter())
let greeter2: Greeter = new greeterMaker();
// 调用 greet() 方法
// 此时 Greeter.standardGreeting 已被修改为 "Hey there!"
// 因为 greeter2.greeting 也是 undefined,所以返回新的静态值
console.log(greeter2.greet()); // 输出: Hey there!
把类当做接口使用
ts
// 定义一个类 Point(点)
// 表示二维平面上的一个点,有 x 和 y 坐标
class Point {
x: number; // x 坐标,类型为 number
y: number; // y 坐标,类型为 number
}
// 定义一个接口 Point3d(三维点)
// 使用 `extends` 继承自 Point 类
// 表示:Point3d 是一个 Point,并且额外有一个 z 坐标
interface Point3d extends Point {
z: number; // z 坐标,类型为 number(第三维)
}
// 声明一个变量 point3d,类型为 Point3d
// 创建一个对象字面量 {x: 1, y: 2, z: 3} 并赋值给它
let point3d: Point3d = {x: 1, y: 2, z: 3};
// 这个赋值是合法的,因为:
// 对象有 x: number, y: number(满足 Point 的结构)
// 还有 z: number(满足 Point3d 的额外要求)
总概: typeScript 类(Classes)------ 官方核心概念总概 TypeScript 的类是对 JavaScript ES6 类的超集,在原有语法基础上增加了 类型系统 和 面向对象高级特性,让开发更安全、更可维护。
- 基本语法(ES6 + 类型)
ts
class Point {
x: number; // 实例属性,带类型
y: number;
// 构造函数
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
// 实例方法
distance(): number {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
}
特点:
支持 constructor、methods、properties 所有成员都可以添加类型注解(: number 等) 2. 访问修饰符(Access Modifiers) 控制类成员的可访问性:
修饰符 含义 public 默认,任何地方可访问 ✅ private 只能在类内部访问 ❌ 子类/外部不可访问 protected 类内部 + 子类可访问,外部不可
ts
class Animal {
private name: string; // 只能在 Animal 内部使用
protected age: number; // 子类可以访问
public species: string; // 任何人都能访问
constructor(name: string, age: number, species: string) {
this.name = name;
this.age = age;
this.species = species;
}
}
编译后这些修饰符会消失(变成普通 JS),但 TS 在编译时做类型检查。
- 继承(Inheritance) ------ extends 支持单继承,子类可重写父类方法。
ts
class Dog extends Animal {
constructor(name: string, age: number) {
super(name, age, "Dog"); // 调用父类构造函数
}
// 重写方法
protected ageUp() {
this.age += 1; // ✅ 可访问 protected 成员
}
}
- 参数属性(Parameter Properties) 简化构造函数写法,一行声明并初始化属性。
ts
class Person {
// 一行搞定:声明 + 赋值 + 访问控制
constructor(public name: string, private id: number) {
// this.name = name;
// this.id = id;
// 自动生成
}
}
等价于:
ts
class Person {
public name: string;
private id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
- 存取器(Getters / Setters) 控制属性的读写逻辑。
ts
class BankAccount {
private _balance: number = 0;
get balance(): number {
return this._balance;
}
set balance(amount: number) {
if (amount < 0) {
throw new Error("Balance cannot be negative");
}
this._balance = amount;
}
}
const account = new BankAccount();
account.balance = 100; // 调用 setter
console.log(account.balance); // 调用 getter
需要设置 tsconfig.json 中 "target": "ES5" 或更高。
- 静态成员(Static Members) 属于类本身,不属于任何实例。
ts
class MathUtils {
static PI = 3.14159;
static circleArea(radius: number): number {
return this.PI * radius ** 2;
}
}
console.log(MathUtils.PI);
console.log(MathUtils.circleArea(5));
- 抽象类(Abstract Classes) 用于定义"模板",不能被实例化,只能被继承。
ts
abstract class Animal {
abstract makeSound(): void; // 子类必须实现
move(): void {
console.log("Moving...");
}
}
class Cat extends Animal {
makeSound(): void {
console.log("Meow!");
}
}
适合用于框架设计、基类约束。
- this 类型(Fluent Interface) 方法返回 this,支持链式调用。
ts
class Calculator {
value: number = 0;
add(n: number): this {
this.value += n;
return this;
}
multiply(n: number): this {
this.value *= n;
return this;
}
}
new Calculator().add(5).multiply(2); // 链式调用 9. 类与接口(Class & Interface) 类可以实现(implements)一个或多个接口,确保结构合规。
ts
interface Drawable {
draw(): void;
}
class Circle implements Drawable {
draw() {
console.log("Drawing a circle");
}
}
接口定义"契约",类负责实现。
- 高级:typeof Class 与 构造函数类型
ts
let ctor: typeof Point = Point; // ctor 是"类本身"的类型
let p = new ctor(1, 2); // 可以 new
用于工厂模式、依赖注入等高级场景。
总结:TypeScript 类的五大优势 特性 作用 类型系统 属性、方法、参数都有类型,减少运行时错误 访问控制 private/protected 提高封装性 继承与多态 支持面向对象设计 抽象类 + 接口 实现复杂架构和约束 编译时检查 开发阶段发现问题,提升代码质量