面向对象设计模式在大型 JavaScript 项目中是服务于可维护性、协作效率和演进弹性的工程纪律;应按业务域组织类与组件,用组合代替继承,封装私有状态,结合模块系统控制可见性边界。

在大型 JavaScript 项目中,面向对象设计模式不是堆砌语法糖,而是服务于可维护性、协作效率和演进弹性的一套工程纪律。关键不在于“用了多少模式”,而在于是否用对了地方、是否收敛了复杂度。
按业务域组织类与组件,而非技术分层
很多团队一上来就建 models/、controllers/、views/ 这类通用目录,结果很快变成跨模块的依赖泥潭。更可持续的做法是:以业务能力为边界划分自包含单元。
- 比如电商项目中,“订单”应是一个独立模块,内含
Order类、OrderService、OrderRepository、OrderValidator及对应测试——所有与订单强相关的逻辑都在同一目录下 - 类之间通过明确接口通信(如
PaymentProcessor.process()),避免直接访问对方内部字段 - 配合 TypeScript 接口定义契约,让 IDE 和编译器能提前捕获越界调用
用组合代替继承,用策略+工厂解耦行为变体
深度继承链(A → B → C → D)在 JS 中极易失控,尤其当多个子类需要交叉复用不同行为时。此时继承不再是简化,而是耦合放大器。
- 把变化点抽成独立类:支付方式(微信/支付宝/银行卡)、通知渠道(短信/邮件/站内信)、导出格式(CSV/PDF/Excel)都做成策略类,统一实现
execute(input)方法 - 用工厂函数或工厂类根据上下文创建具体策略实例,上层业务类只依赖抽象策略接口
- 实际项目中,这种写法使新增一种支付方式只需新增一个策略类+注册到工厂,无需修改任何已有类
封装私有状态,暴露最小必要接口
JS 的封装能力比很多人以为的更强。ES2022 私有字段(#field)和闭包都能有效隔离内部实现。
- 敏感数据(如 token、密码、临时缓存)必须用
#前缀声明,杜绝外部意外读写 - 构造函数中初始化的依赖(如数据库连接、第三方 SDK 实例)也建议设为私有,仅通过公开方法提供受控操作
- 对外只暴露动词方法(
submit()、cancel()、retry()),避免暴露 setter 或直接修改状态的入口
结合模块系统控制可见性边界
类封装解决的是“运行时访问控制”,而 ES 模块(export/import)解决的是“编译时可见性”。两者叠加才能形成真正健壮的边界。
- 每个业务模块的入口文件(如
order/index.js)只export对外承诺的类和函数,其余全部默认不导出 - 禁止跨模块直接导入内部工具类(如
import { validateEmail } from '../../utils/validation'),应通过所在模块的公共 API 间接使用 - 借助 ESLint 规则(如
no-restricted-imports)强制执行模块边界,把约束落到开发流程里
不复杂但容易忽略:好的面向对象实践,本质是持续做减法——减少依赖、减少暴露、减少假设。模式只是工具,目标始终是让下一位阅读代码的人,三秒内看懂这个模块负责什么、不能做什么、该怎么用。


















