核心是将行为解耦为可装配、可测试、可替换的函数或对象模块,并通过显式委托替代隐式原型链复用;剥离伪继承,改用模块导入、hook、组合组件和配置驱动工厂实现灵活扩展与渐进迁移。

在原型体系(如 JavaScript)下基于 Composition over Inheritance 重构大型项目,核心不是“去掉所有 class extends”,而是把行为解耦为可装配、可测试、可替换的函数或对象模块,并通过显式委托而非隐式原型链调用实现复用。
识别并剥离“伪继承”结构
原型链中常见误用:为复用工具方法而让业务类继承一个“BaseUtils”或“ControllerMixin”类。这类继承往往只为了共享静态方法或通用逻辑,不满足 is-a 关系,且破坏封装。
- 检查所有
class A extends B,确认 B 是否真正定义了 A 的类型语义(例如AdminUser extends User合理;OrderService extends LoggerHelper不合理) - 将仅用于复用的“父类”重构为独立模块:把
BaseUtils.readFile()拆成import { readFile } from '@/utils/io' - 对仍需状态协同的场景(如共享 loading 状态、错误重试逻辑),改用高阶函数或 Hook 封装,而非继承
用对象组合替代原型链委托
避免依赖 this.__proto__ 或 Object.getPrototypeOf(this) 隐式查找方法。改为在实例上显式持有能力模块,并调用其方法。
- 将原本分散在多个父级原型上的行为,提取为职责单一的组件类:
AuthManager、CachePolicy、RetryStrategy - 在主类构造时注入这些组件:
this.auth = new AuthManager(config),而非super(config) - 方法内部直接委托:
this.auth.login(),而非super.login()—— 调用路径清晰,便于打桩测试和运行时替换
利用工厂 + 配置驱动行为装配
大型项目常有环境差异(开发/测试/生产)、租户差异(A 客户要微信登录,B 客户要 LDAP)。硬编码继承分支会导致类爆炸,而组合支持运行时装配。
- 定义能力接口(TypeScript 接口或 JSDoc @typedef):
interface PaymentProcessor { charge(amount: number): Promise<receipt> }</receipt> - 编写多个实现:
WechatPayment、AlipayPayment、FakePayment - 在启动阶段根据配置创建实例:
const processor = createPaymentProcessor(env.config.payment.type),再传入业务对象 - 后续扩展新支付方式,无需修改任何已有类,只新增实现 + 注册映射
渐进迁移策略:保留兼容但禁用新增继承
全量重写不现实。可设定清晰的演进节奏:
- 新功能一律禁止使用
extends复用非领域父类;必须用组合 + 依赖注入 - 存量继承链标注 @deprecated,并为每个被继承的“工具父类”提供等效的组合式替代包(如
@/composition/useAuth) - 关键模块(如数据请求层、表单管理器)优先重构,因其复用频次高、副作用明显,收益最大
- 配合 ESLint 规则(如
no-useless-constructor、no-prototype-builtins)和自定义规则拦截extends BaseXxx新增










