原型模式实现依赖注入的核心是构造函数注入,即依赖作为参数传入构造函数并赋值给this,确保每个实例持有专属依赖;需避免污染原型链、硬编码或延迟赋值,推荐配合工厂函数或组合方式提升可测试性与解耦度。

在无框架依赖的项目中,用原型模式实现依赖注入,核心不是“往 prototype 上塞东西”,而是让每个实例在创建时就持有自己专属的依赖,既保持继承链清晰,又避免共享状态和硬编码。
构造函数注入:最直接可靠的方式
这是原型继承下最自然、最可控的做法。依赖作为参数传入构造函数,并赋值给 this,后续方法全部通过实例属性访问。
- 父类明确声明所需依赖(如
api、logger),并在构造函数中接收并保存 - 子类构造函数必须调用
Parent.call(this, ...),确保依赖被正确初始化 - 所有业务逻辑通过
this.api.fetch()这类方式使用,不查原型、不读全局
避免污染原型链的常见陷阱
原型是共享的,任何挂载到 prototype 上的对象或函数,都会被所有实例共用——这极易引发状态冲突和测试困难。
- 不要写
Child.prototype.api = new Api()→ 所有子实例共用同一个 api 实例 - 不要在构造函数外从
window或单例模块里取依赖 → 无法替换、无法 mock - 不要用
Object.assign(this, { api })在实例创建后补依赖 → 初始化逻辑分散,违反封装原则
配合工厂函数统一管理实例创建
当依赖来源多样(比如不同环境用不同 API 实现),可封装一个工厂函数,集中处理依赖组装逻辑:
- 工厂接收具体依赖(如
fetcher、storage)并返回构造函数 - 子类可复用同一父类构造逻辑,只需传入不同依赖组合
- 单元测试时直接传入 mock 依赖,无需修改源码或打桩
需要动态传递时:用组合代替继承
如果父类逻辑需依赖子类提供的能力(比如分页组件要调用子类定义的数据获取方法),与其强行继承,不如采用组合:
- 父类接收一个配置对象,其中包含子类实现的函数(如
loadData: () => Promise) - 子类实例化父类时传入自己的实现,父类只负责调用,不关心来源
- 这种方式解耦更彻底,比 rely on
instanceof或反向查找祖先更符合依赖倒置原则











