0065. 类与函数的选择
- 1. 🎯 本节内容
- 2. 🫧 评价
- 3. 🤔 在实际开发中,什么时候需要写类,什么时候需要写函数呢?
- 4. 🤔 使用类还是函数,对性能会有什么影响?
- 5. 🤔 我们在什么情况下需要考虑将模块中的函数重构为类?
1. 🎯 本节内容
- 类与函数的选择策略
2. 🫧 评价
选择用“类”还是“函数”的核心原则:优先选择最简单能满足需求的方案。
- 如果函数足够解决问题,就不要使用类
- 当状态和行为变得复杂时,再考虑升级为类
以下是一些经典场景以及推荐的选择:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 领域模型/实体 | 类 | 表示“是什么”,封装数据和行为 |
| 工具函数/格式化 | 函数 | 表示“做什么”,无状态更简单 |
| 需要严格封装状态 | 类(配合 # 私有字段) | 运行时真正的私有性 |
| 需要实现特定接口 | 类 | 接口设计面向对象 |
| React 组件 | 函数组件 | 现代 React 的最佳实践 |
| 简单数据转换 | 纯函数 | 易测试、无副作用、可组合 |
| 需要多态行为 | 类 | 支持继承和方法重写 |
| 配置驱动的逻辑 | 函数(工厂函数) | 更灵活的配置方式 |
3. 🤔 在实际开发中,什么时候需要写类,什么时候需要写函数呢?
类跟函数非常相似,在实际开发中你会发现很多地方写类或者写函数感觉都行,如果选择起来很困难的话,那么可以参考以下这个简单的策略:
- 多内部状态 + 多方法维护内部状态数据 -> 用类
- 几乎无内部状态 + 几乎不处理内部状态数据 -> 用函数
3.1. 写类(class)的情况
- 需要封装状态 + 行为 -> 例如:一个服务要持有配置(如 baseURL)、同时提供方法(如 fetchUser)。
- 需要访问控制(private / protected) -> 例如:内部状态不能被外部直接修改(如用户余额)。
- 需要实现接口(implements)或多态 -> 例如:多个类实现同一个
Drawable接口,供统一调用。 - 需要继承(extends)复用逻辑 -> 例如:
BaseApiService提供通用请求方法,UserService继承它。
3.2. 写函数(纯函数)的情况
- 无状态的数据处理 -> 例如:格式化日期、校验邮箱、转换数组。
- 逻辑复用但不涉及状态管理 -> 例如:自定义 React Hook(如
useDebounce)、工具函数。 - 输入决定输出,无副作用 -> 相同输入永远得到相同输出,不修改外部变量、不发请求、不操作 DOM。
4. 🤔 使用类还是函数,对性能会有什么影响?
内存占用:
- 类实例会创建完整对象,每个实例有独立方法副本(除非使用原型)
- 函数通常更轻量,特别是纯函数
Tree Shaking:
- 独立函数更容易被未使用代码消除
- 类的方法可能难以完全摇树优化
实际建议:性能差异通常微小,优先考虑代码清晰度和可维护性
5. 🤔 我们在什么情况下需要考虑将模块中的函数重构为类?
下面是需要考虑将函数重构为类的一些信号:
- 发现模块内的函数集合共享相同输入参数、内部状态、外部依赖
- 有一些内部状态需要保护,不被意外修改
- 需要使用模块内的函数集合实现某个特定接口契约
- ……
示例:
ts
// 重构前:分散的函数
function validateUser(user: User) {
/*...*/
}
function saveUser(user: User) {
/*...*/
}
function getUserStats(user: User) {
/*...*/
}
// 重构后:统一的类
class UserService {
constructor(private db: Database) {}
validate(user: User) {
/*...*/
}
save(user: User) {
/*...*/
}
getStats(user: User) {
/*...*/
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25