javascript原型链不直接实现依赖注入,而是通过构造函数接收依赖并赋值给实例,原型方法通过this访问,从而兼顾内存节省与依赖可替换性。

依赖不藏在原型里,而是注入到实例上
原型链适合共享行为(方法),不适合存放状态或外部依赖。把 API 客户端、配置对象等依赖直接挂到 prototype 上会导致所有实例共享同一份引用,引发状态污染或并发问题。
正确做法是:在构造函数中接收依赖,并赋值给 实例自身(this.api = api),再让原型方法通过 this.api 调用:
- 构造函数负责“接收并保存”依赖
- 原型方法负责“使用”依赖,不关心它怎么来
- 这样既利用了原型节省内存,又保持了依赖可替换、可测试
用原型方法统一调用注入的依赖
比如一个通用的数据获取类,它的行为(fetch)写在原型上,但具体用哪个 HTTP 客户端由使用者决定:
class DataService {
constructor(httpClient) {
this.http = httpClient; // 注入到实例
}
}
DataService.prototype.fetch = function(url) {
return this.http.get(url); // 原型方法只管用,不管造
};
使用时可以轻松换实现:
-
new DataService(fetch)→ 浏览器原生 fetch -
new DataService(axios)→ 第三方库 -
new DataService({ get: () => Promise.resolve({ ok: true }) })→ 单元测试用 mock
避免误用原型“模拟自动注入”
有人尝试这样写:
Service.prototype.api = new ApiClient(); // ❌ 错误:所有实例共享同一个实例
这看似“省事”,实则违反 DI 原则,带来以下问题:
- 无法为不同实例配不同配置(如不同 base URL)
- 测试时无法单独替换某次调用的依赖
- 若
ApiClient有内部状态(如 token、缓存),多个实例会互相干扰
真正解耦的关键,是把“谁提供依赖”的控制权交给调用方,而不是让原型替你决定。
进阶:结合原型与注册表做轻量服务定位
如果你希望多个类共用同一套服务(如全局 logger、config),可以用一个“服务注册表” + 原型辅助访问:
const services = {
logger: console,
config: { env: 'dev' }
};
class BaseService {
get logger() { return services.logger; }
get config() { return services.config; }
}
BaseService.prototype.log = function(msg) {
this.logger.log('[INFO]', msg);
};
这里没有“自动注入”,而是约定式访问。优点是简单;缺点是耦合了全局注册表。适合小项目或脚手架,但不推荐在大型应用中替代显式注入。
不复杂但容易忽略:依赖注入的本质是控制权转移,不是技术炫技。原型链是工具,不是注入器。用好它,关键在分清“共享逻辑”和“独享依赖”的边界。Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











